aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMalte Voos <git@mal.tc>2025-12-05 15:35:38 +0100
committerMalte Voos <git@mal.tc>2025-12-05 15:43:58 +0100
commitc347b6133365dcf1b7da4e77890b20d04d6cfba4 (patch)
treec83aac6f7d1e6edc57e607f01e5d3eeee8da4a0e
parent652b1c2a0ce7db4885ebc51f7f09133a43401442 (diff)
downloadlleap-c347b6133365dcf1b7da4e77890b20d04d6cfba4.tar.gz
lleap-c347b6133365dcf1b7da4e77890b20d04d6cfba4.zip
implement machine translation; various fixes and refactorings
-rw-r--r--Cargo.lock1467
-rw-r--r--Cargo.toml2
-rw-r--r--data/style.css (renamed from resources/style.css)4
-rw-r--r--flake.nix1
-rw-r--r--src/app.rs335
-rw-r--r--src/cue_view.rs81
-rw-r--r--src/main.rs9
-rw-r--r--src/open_dialog.rs58
-rw-r--r--src/preferences_dialog.rs (renamed from src/preferences.rs)29
-rw-r--r--src/settings.rs5
-rw-r--r--src/subtitle_selection_dialog.rs148
-rw-r--r--src/subtitle_view.rs57
-rw-r--r--src/subtitles/extraction/embedded.rs (renamed from src/subtitle_extraction/embedded.rs)18
-rw-r--r--src/subtitles/extraction/mod.rs (renamed from src/subtitle_extraction/mod.rs)12
-rw-r--r--src/subtitles/extraction/whisper.rs (renamed from src/subtitle_extraction/whisper.rs)16
-rw-r--r--src/subtitles/mod.rs86
-rw-r--r--src/subtitles/state.rs63
-rw-r--r--src/track_selector.rs26
-rw-r--r--src/tracks.rs38
-rw-r--r--src/transcript.rs8
-rw-r--r--src/translation/deepl.rs106
-rw-r--r--src/translation/mod.rs11
-rw-r--r--src/util/tracker.rs18
-rw-r--r--subs.srt644
24 files changed, 2172 insertions, 1070 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f3b1d1c..6a00a96 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -62,7 +62,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -73,7 +73,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -83,6 +83,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
name = "async-channel"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -95,6 +101,12 @@ dependencies = [
]
[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
name = "atomic_refcell"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -107,6 +119,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -121,7 +139,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -143,6 +161,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
+name = "bytes"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
+
+[[package]]
name = "cairo-rs"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -231,9 +255,9 @@ dependencies = [
"bitflags",
"block",
"cocoa-foundation",
- "core-foundation",
+ "core-foundation 0.10.1",
"core-graphics",
- "foreign-types",
+ "foreign-types 0.5.0",
"libc",
"objc",
]
@@ -246,7 +270,7 @@ checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d"
dependencies = [
"bitflags",
"block",
- "core-foundation",
+ "core-foundation 0.10.1",
"core-graphics-types",
"objc",
]
@@ -268,6 +292,16 @@ dependencies = [
[[package]]
name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
@@ -289,9 +323,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
dependencies = [
"bitflags",
- "core-foundation",
+ "core-foundation 0.10.1",
"core-graphics-types",
- "foreign-types",
+ "foreign-types 0.5.0",
"libc",
]
@@ -302,7 +336,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
dependencies = [
"bitflags",
- "core-foundation",
+ "core-foundation 0.10.1",
"libc",
]
@@ -322,12 +356,89 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
+name = "deepl"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e315350d74306624c6519aea28860100be984dfbec7939a36d6b190fc0b77fb1"
+dependencies = [
+ "paste",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "tokio-stream",
+ "typed-builder",
+]
+
+[[package]]
+name = "deluxe"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed332aaf752b459088acf3dd4eca323e3ef4b83c70a84ca48fb0ec5305f1488"
+dependencies = [
+ "deluxe-core",
+ "deluxe-macros",
+ "once_cell",
+ "proc-macro2",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "deluxe-core"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddada51c8576df9d6a8450c351ff63042b092c9458b8ac7d20f89cbd0ffd313"
+dependencies = [
+ "arrayvec",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "deluxe-macros"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87546d9c837f0b7557e47b8bd6eae52c3c223141b76aa233c345c9ab41d9117"
+dependencies = [
+ "deluxe-core",
+ "heck 0.4.1",
+ "if_chain",
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
+]
+
+[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "endi"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -363,6 +474,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "event-listener"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -384,6 +505,12 @@ dependencies = [
]
[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
name = "ffmpeg-next"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -447,13 +574,28 @@ dependencies = [
]
[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared 0.1.1",
+]
+
+[[package]]
name = "foreign-types"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
- "foreign-types-shared",
+ "foreign-types-shared 0.3.1",
]
[[package]]
@@ -464,16 +606,31 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "foreign-types-shared"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
name = "fragile"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -535,7 +692,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -664,6 +821,18 @@ dependencies = [
]
[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
name = "gio"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -690,7 +859,7 @@ dependencies = [
"gobject-sys",
"libc",
"system-deps",
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -720,11 +889,11 @@ version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb"
dependencies = [
- "heck",
- "proc-macro-crate",
+ "heck 0.5.0",
+ "proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -778,6 +947,22 @@ dependencies = [
]
[[package]]
+name = "gsettings-macro"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f3b38a7ca9498c2a7ed01ec9cff1f07397d8bc8f4a345b46a58a80170947263"
+dependencies = [
+ "deluxe",
+ "heck 0.5.0",
+ "proc-macro-error",
+ "proc-macro2",
+ "quick-xml 0.37.5",
+ "quote",
+ "serde",
+ "syn 2.0.110",
+]
+
+[[package]]
name = "gsk4"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -822,7 +1007,7 @@ dependencies = [
"gstreamer-gl",
"gstreamer-video",
"gtk4",
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -832,7 +1017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68a894ef2d738054b950e1dbef5d9012b63fd968d4d32dbccd31bd8d8d4b219"
dependencies = [
"chrono",
- "toml_edit",
+ "toml_edit 0.23.7",
]
[[package]]
@@ -1013,10 +1198,10 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "821160b4f17e7e4ed748818c23682d0a46bed04c287dbaac54dd4869d2c5e06a"
dependencies = [
- "proc-macro-crate",
+ "proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -1045,7 +1230,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d94c8a63f94bbc35cf63e105791c5992bd60d4516d41fe5bf3db8d10b30b43"
dependencies = [
"flate2",
- "quick-xml",
+ "quick-xml 0.38.4",
"serde",
"serde_json",
"walkdir",
@@ -1054,6 +1239,25 @@ dependencies = [
]
[[package]]
+name = "h2"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1061,6 +1265,12 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
@@ -1072,6 +1282,125 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
+name = "http"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "pin-utils",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "system-configuration",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "windows-registry",
+]
+
+[[package]]
name = "iana-time-zone"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1096,6 +1425,114 @@ dependencies = [
]
[[package]]
+name = "icu_collections"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+
+[[package]]
+name = "icu_properties"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
+
+[[package]]
+name = "icu_provider"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "if_chain"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
+
+[[package]]
name = "indexmap"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1106,6 +1543,22 @@ dependencies = [
]
[[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "iri-string"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1164,7 +1617,7 @@ checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -1234,14 +1687,28 @@ dependencies = [
]
[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "litemap"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+
+[[package]]
name = "lleap"
version = "0.1.0"
dependencies = [
"anyhow",
"async-channel",
"cocoa",
+ "deepl",
"env_logger",
"ffmpeg-next",
+ "gsettings-macro",
"gst-plugin-gtk4",
"gstreamer",
"gstreamer-play",
@@ -1302,6 +1769,22 @@ dependencies = [
]
[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1318,6 +1801,17 @@ dependencies = [
]
[[package]]
+name = "mio"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "muldiv"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1329,7 +1823,24 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
- "getrandom",
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
]
[[package]]
@@ -1402,6 +1913,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
+name = "openssl"
+version = "0.10.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types 0.3.2",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "option-operations"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1441,12 +1996,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
name = "pastey"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1498,12 +2065,55 @@ dependencies = [
]
[[package]]
+name = "potential_utf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
name = "proc-macro-crate"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
dependencies = [
- "toml_edit",
+ "toml_edit 0.23.7",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
]
[[package]]
@@ -1517,6 +2127,16 @@ dependencies = [
[[package]]
name = "quick-xml"
+version = "0.37.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "quick-xml"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
@@ -1535,6 +2155,12 @@ dependencies = [
]
[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1627,7 +2253,65 @@ checksum = "175fce497fc6f11dde7ea56daa30ff7ad29a534bbc209d59d766659c880ba5f1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.12.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "mime_guess",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.16",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -1646,6 +2330,52 @@ dependencies = [
]
[[package]]
+name = "rustix"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
+dependencies = [
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1667,12 +2397,44 @@ dependencies = [
]
[[package]]
+name = "schannel"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags",
+ "core-foundation 0.9.4",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1705,7 +2467,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -1731,6 +2493,18 @@ dependencies = [
]
[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1761,6 +2535,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
+name = "socket2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1770,12 +2554,40 @@ dependencies = [
]
[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "unicode-ident",
+]
+
+[[package]]
name = "syn"
version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1787,13 +2599,54 @@ dependencies = [
]
[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
+dependencies = [
+ "bitflags",
+ "core-foundation 0.9.4",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
name = "system-deps"
version = "7.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f"
dependencies = [
"cfg-expr",
- "heck",
+ "heck 0.5.0",
"pkg-config",
"toml",
"version-compare",
@@ -1806,6 +2659,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
[[package]]
+name = "tempfile"
+version = "3.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.4",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1822,7 +2688,17 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+dependencies = [
+ "displaydoc",
+ "zerovec",
]
[[package]]
@@ -1831,7 +2707,68 @@ version = "1.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [
+ "bytes",
+ "libc",
+ "mio",
"pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
]
[[package]]
@@ -1843,14 +2780,20 @@ dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
- "toml_datetime",
+ "toml_datetime 0.7.3",
"toml_parser",
"toml_writer",
- "winnow",
+ "winnow 0.7.13",
]
[[package]]
name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+
+[[package]]
+name = "toml_datetime"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
@@ -1860,14 +2803,25 @@ dependencies = [
[[package]]
name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime 0.6.11",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
version = "0.23.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
dependencies = [
"indexmap",
- "toml_datetime",
+ "toml_datetime 0.7.3",
"toml_parser",
- "winnow",
+ "winnow 0.7.13",
]
[[package]]
@@ -1876,7 +2830,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
dependencies = [
- "winnow",
+ "winnow 0.7.13",
]
[[package]]
@@ -1886,6 +2840,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
[[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "iri-string",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1904,7 +2903,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -1933,10 +2932,42 @@ checksum = "dc19eb2373ccf3d1999967c26c3d44534ff71ae5d8b9dacf78f4b13132229e48"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typed-builder"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd9d30e3a08026c78f246b173243cf07b3696d274debd26680773b6773c2afc7"
+dependencies = [
+ "typed-builder-macro",
+]
+
+[[package]]
+name = "typed-builder-macro"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
]
[[package]]
+name = "unicase"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
+
+[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1949,6 +2980,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1967,6 +3022,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1977,12 +3038,30 @@ dependencies = [
]
[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
name = "wasm-bindgen"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1996,6 +3075,19 @@ dependencies = [
]
[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
name = "wasm-bindgen-macro"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2014,7 +3106,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
"wasm-bindgen-shared",
]
@@ -2028,12 +3120,35 @@ dependencies = [
]
[[package]]
+name = "wasm-streams"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2057,7 +3172,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -2068,7 +3183,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
]
[[package]]
@@ -2078,6 +3193,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
+name = "windows-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2097,6 +3223,24 @@ dependencies = [
[[package]]
name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
@@ -2105,6 +3249,144 @@ dependencies = [
]
[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "winnow"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2114,6 +3396,41 @@ dependencies = [
]
[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "writeable"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+
+[[package]]
+name = "yoke"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
+ "synstructure",
+]
+
+[[package]]
name = "zerocopy"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2130,7 +3447,67 @@ checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.110",
]
[[package]]
@@ -2141,7 +3518,7 @@ checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c"
dependencies = [
"endi",
"serde",
- "winnow",
+ "winnow 0.7.13",
"zvariant_derive",
"zvariant_utils",
]
@@ -2152,10 +3529,10 @@ version = "5.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006"
dependencies = [
- "proc-macro-crate",
+ "proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.110",
"zvariant_utils",
]
@@ -2168,6 +3545,6 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
- "syn",
- "winnow",
+ "syn 2.0.110",
+ "winnow 0.7.13",
]
diff --git a/Cargo.toml b/Cargo.toml
index 13ff3a6..5f7af0f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,6 +23,8 @@ isolang = { git = "https://github.com/humenda/isolang-rs" }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
relm4-icons = "0.10"
+deepl = "0.7.3"
+gsettings-macro = "0.2.2"
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.26"
diff --git a/resources/style.css b/data/style.css
index 44106e1..03dc022 100644
--- a/resources/style.css
+++ b/data/style.css
@@ -5,6 +5,10 @@
border-radius: 12px;
}
+.cue-view:disabled {
+ opacity: 0;
+}
+
.cue-view link {
color: @theme_fg_color;
text-decoration: none;
diff --git a/flake.nix b/flake.nix
index af7d9f5..5469f03 100644
--- a/flake.nix
+++ b/flake.nix
@@ -52,6 +52,7 @@
];
buildInputs = with pkgs; [
+ openssl
gtk4
libadwaita
ffmpeg_8-full.dev
diff --git a/src/app.rs b/src/app.rs
index 951392e..bdb2ef9 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -5,33 +5,36 @@ use crate::{
icon_names,
open_dialog::{OpenDialog, OpenDialogMsg, OpenDialogOutput},
player::{Player, PlayerMsg, PlayerOutput},
- preferences::{Preferences, PreferencesMsg},
- subtitle_extraction::{SubtitleExtractor, SubtitleExtractorMsg, SubtitleExtractorOutput},
+ preferences_dialog::{PreferencesDialog, PreferencesDialogMsg},
subtitle_selection_dialog::{
SubtitleSelectionDialog, SubtitleSelectionDialogMsg, SubtitleSelectionDialogOutput,
+ SubtitleSettings,
},
subtitle_view::{SubtitleView, SubtitleViewMsg, SubtitleViewOutput},
- tracks::{SUBTITLE_TRACKS, StreamIndex, SubtitleCue},
+ subtitles::{
+ MetadataCollection, SUBTITLE_TRACKS, StreamIndex, SubtitleCue, SubtitleTrack,
+ extraction::{SubtitleExtractor, SubtitleExtractorMsg, SubtitleExtractorOutput},
+ state::SubtitleState,
+ },
transcript::{Transcript, TranscriptMsg, TranscriptOutput},
+ translation::{DeeplTranslator, deepl::DeeplTranslatorMsg},
util::Tracker,
};
pub struct App {
+ root: adw::ApplicationWindow,
transcript: Controller<Transcript>,
player: Controller<Player>,
subtitle_view: Controller<SubtitleView>,
extractor: WorkerController<SubtitleExtractor>,
+ deepl_translator: AsyncController<DeeplTranslator>,
- preferences: Controller<Preferences>,
+ preferences: Controller<PreferencesDialog>,
open_url_dialog: Controller<OpenDialog>,
- subtitle_selection_dialog: Controller<SubtitleSelectionDialog>,
+ subtitle_selection_dialog: Option<Controller<SubtitleSelectionDialog>>,
- primary_stream_ix: Option<StreamIndex>,
- primary_cue: Tracker<Option<String>>,
- primary_last_cue_ix: Tracker<Option<usize>>,
- secondary_cue: Tracker<Option<String>>,
- secondary_stream_ix: Option<StreamIndex>,
- secondary_last_cue_ix: Tracker<Option<usize>>,
+ primary_subtitle_state: SubtitleState,
+ secondary_subtitle_state: SubtitleState,
// for auto-pausing
autopaused: bool,
@@ -40,10 +43,9 @@ pub struct App {
#[derive(Debug)]
pub enum AppMsg {
- NewCue(StreamIndex, SubtitleCue),
+ AddCue(StreamIndex, SubtitleCue),
SubtitleExtractionComplete,
- PrimarySubtitleTrackSelected(Option<StreamIndex>),
- SecondarySubtitleTrackSelected(Option<StreamIndex>),
+ ApplySubtitleSettings(SubtitleSettings),
PositionUpdate(gst::ClockTime),
SetHoveringSubtitleCue(bool),
ShowUrlOpenDialog,
@@ -51,6 +53,7 @@ pub enum AppMsg {
ShowSubtitleSelectionDialog,
Play {
url: String,
+ metadata: MetadataCollection,
whisper_stream_index: Option<StreamIndex>,
},
}
@@ -123,52 +126,46 @@ impl SimpleComponent for App {
sender.input_sender(),
|output| match output {
SubtitleExtractorOutput::NewCue(stream_index, cue) => {
- AppMsg::NewCue(stream_index, cue)
+ AppMsg::AddCue(stream_index, cue)
}
SubtitleExtractorOutput::ExtractionComplete => AppMsg::SubtitleExtractionComplete,
},
);
- let preferences = Preferences::builder().launch(root.clone().into()).detach();
+ let deepl_translator = DeeplTranslator::builder().launch(()).detach();
+
+ let preferences = PreferencesDialog::builder()
+ .launch(root.clone().into())
+ .detach();
let open_url_dialog = OpenDialog::builder().launch(root.clone().into()).forward(
sender.input_sender(),
|output| match output {
OpenDialogOutput::Play {
url,
+ metadata,
whisper_stream_index,
} => AppMsg::Play {
url,
+ metadata,
whisper_stream_index,
},
},
);
- let subtitle_selection_dialog = SubtitleSelectionDialog::builder()
- .launch(root.clone().into())
- .forward(sender.input_sender(), |output| match output {
- SubtitleSelectionDialogOutput::PrimaryTrackSelected(ix) => {
- AppMsg::PrimarySubtitleTrackSelected(ix)
- }
- SubtitleSelectionDialogOutput::SecondaryTrackSelected(ix) => {
- AppMsg::SecondarySubtitleTrackSelected(ix)
- }
- });
let model = Self {
+ root: root.clone(),
player,
transcript,
subtitle_view,
extractor,
+ deepl_translator,
preferences,
open_url_dialog,
- subtitle_selection_dialog,
+ subtitle_selection_dialog: None,
- primary_stream_ix: None,
- primary_cue: Tracker::new(None),
- primary_last_cue_ix: Tracker::new(None),
- secondary_stream_ix: None,
- secondary_cue: Tracker::new(None),
- secondary_last_cue_ix: Tracker::new(None),
+ primary_subtitle_state: SubtitleState::default(),
+ secondary_subtitle_state: SubtitleState::default(),
autopaused: false,
hovering_primary_cue: false,
@@ -179,94 +176,45 @@ impl SimpleComponent for App {
ComponentParts { model, widgets }
}
- fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
+ fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
match message {
- AppMsg::NewCue(stream_index, cue) => {
+ AppMsg::AddCue(stream_ix, cue) => {
+ SUBTITLE_TRACKS
+ .write()
+ .get_mut(&stream_ix)
+ .unwrap()
+ .push_cue(cue.clone());
+
self.transcript
.sender()
- .send(TranscriptMsg::NewCue(stream_index, cue))
+ .send(TranscriptMsg::NewCue(stream_ix, cue))
.unwrap();
}
AppMsg::SubtitleExtractionComplete => {
log::info!("Subtitle extraction complete");
}
- AppMsg::PrimarySubtitleTrackSelected(stream_index) => {
- self.primary_stream_ix = stream_index;
+ AppMsg::ApplySubtitleSettings(settings) => {
+ self.primary_subtitle_state
+ .set_stream_ix(settings.primary_track_ix);
+ self.secondary_subtitle_state
+ .set_stream_ix(settings.secondary_track_ix);
self.transcript
.sender()
- .send(TranscriptMsg::SelectTrack(stream_index))
+ .send(TranscriptMsg::SelectTrack(settings.primary_track_ix))
+ .unwrap();
+ self.deepl_translator
+ .sender()
+ .send(DeeplTranslatorMsg::SelectTrack(settings.primary_track_ix))
.unwrap();
- }
- AppMsg::SecondarySubtitleTrackSelected(stream_index) => {
- self.secondary_stream_ix = stream_index;
- }
- AppMsg::PositionUpdate(pos) => {
- if let Some(stream_ix) = self.primary_stream_ix {
- // sometimes we get a few position update messages after
- // auto-pausing; this prevents us from immediately un-autopausing
- // again
- if self.autopaused {
- return;
- }
-
- let cue_was_some = self.primary_cue.get().is_some();
-
- Self::update_cue(
- stream_ix,
- pos,
- &mut self.primary_cue,
- &mut self.primary_last_cue_ix,
- );
-
- if self.primary_cue.is_dirty() {
- // last cue just ended -> auto-pause
- if cue_was_some && self.hovering_primary_cue {
- self.player.sender().send(PlayerMsg::Pause).unwrap();
- self.autopaused = true;
- return;
- }
-
- self.subtitle_view
- .sender()
- .send(SubtitleViewMsg::SetPrimaryCue(
- self.primary_cue.get().clone(),
- ))
- .unwrap();
-
- self.primary_cue.reset();
- }
-
- if self.primary_last_cue_ix.is_dirty() {
- if let Some(ix) = self.primary_last_cue_ix.get() {
- self.transcript
- .sender()
- .send(TranscriptMsg::ScrollToCue(*ix))
- .unwrap();
- }
- self.primary_last_cue_ix.reset();
- }
- }
- if let Some(stream_ix) = self.secondary_stream_ix {
- Self::update_cue(
- stream_ix,
- pos,
- &mut self.secondary_cue,
- &mut self.secondary_last_cue_ix,
- );
-
- if !self.autopaused && self.secondary_cue.is_dirty() {
- self.subtitle_view
- .sender()
- .send(SubtitleViewMsg::SetSecondaryCue(
- self.secondary_cue.get().clone(),
- ))
- .unwrap();
-
- self.secondary_cue.reset();
- }
- }
+ self.subtitle_view
+ .sender()
+ .send(SubtitleViewMsg::ApplySubtitleSettings(settings))
+ .unwrap();
+ }
+ AppMsg::PositionUpdate(position) => {
+ self.update_subtitle_states(position);
}
AppMsg::SetHoveringSubtitleCue(hovering) => {
self.hovering_primary_cue = hovering;
@@ -284,17 +232,20 @@ impl SimpleComponent for App {
AppMsg::ShowPreferences => {
self.preferences
.sender()
- .send(PreferencesMsg::Show)
+ .send(PreferencesDialogMsg::Show)
.unwrap();
}
AppMsg::ShowSubtitleSelectionDialog => {
- self.subtitle_selection_dialog
- .sender()
- .send(SubtitleSelectionDialogMsg::Show)
- .unwrap();
+ if let Some(ref dialog) = self.subtitle_selection_dialog {
+ dialog
+ .sender()
+ .send(SubtitleSelectionDialogMsg::Show)
+ .unwrap();
+ }
}
AppMsg::Play {
url,
+ metadata,
whisper_stream_index,
} => {
self.player
@@ -308,70 +259,128 @@ impl SimpleComponent for App {
whisper_stream_index,
})
.unwrap();
+
+ let subtitle_selection_dialog = SubtitleSelectionDialog::builder()
+ .launch((self.root.clone().into(), metadata))
+ .forward(sender.input_sender(), |output| match output {
+ SubtitleSelectionDialogOutput::ApplySubtitleSettings(settings) => {
+ AppMsg::ApplySubtitleSettings(settings)
+ }
+ });
+ self.subtitle_selection_dialog = Some(subtitle_selection_dialog);
}
}
}
}
impl App {
- fn update_cue(
- stream_ix: StreamIndex,
- position: gst::ClockTime,
- cue: &mut Tracker<Option<String>>,
- last_cue_ix: &mut Tracker<Option<usize>>,
- ) {
- let lock = SUBTITLE_TRACKS.read();
- let track = lock.get(&stream_ix).unwrap();
+ fn update_subtitle_states(&mut self, position: gst::ClockTime) {
+ self.update_primary_subtitle_state(position);
+ self.update_secondary_subtitle_state(position);
+ }
- // try to find current cue quickly (should usually succeed during playback)
- if let Some(ix) = last_cue_ix.get() {
- let last_cue = track.cues.get(*ix).unwrap();
- if last_cue.start <= position && position <= last_cue.end {
- // still at current cue
- return;
- } else if let Some(next_cue) = track.cues.get(ix + 1) {
- if last_cue.end < position && position < next_cue.start {
- // strictly between cues
- cue.set(None);
- return;
- }
- if next_cue.start <= position && position <= next_cue.end {
- // already in next cue (this happens when one cue immediately
- // follows the previous one)
- cue.set(Some(next_cue.text.clone()));
- last_cue_ix.set(Some(ix + 1));
- return;
- }
+ fn update_primary_subtitle_state(&mut self, position: gst::ClockTime) {
+ // sometimes we get a few position update messages after
+ // auto-pausing
+ if self.autopaused {
+ return;
+ }
+
+ update_subtitle_state(&mut self.primary_subtitle_state, position);
+
+ // last cue just ended -> auto-pause
+ if self.primary_subtitle_state.last_ended_cue_ix.is_dirty() && self.hovering_primary_cue {
+ self.player.sender().send(PlayerMsg::Pause).unwrap();
+ self.autopaused = true;
+ return;
+ }
+
+ if self.primary_subtitle_state.is_dirty() {
+ let cue = self.primary_subtitle_state.active_cue();
+
+ self.subtitle_view
+ .sender()
+ .send(SubtitleViewMsg::SetPrimaryCue(cue))
+ .unwrap();
+ }
+
+ if self.primary_subtitle_state.last_started_cue_ix.is_dirty() {
+ if let Some(ix) = *self.primary_subtitle_state.last_started_cue_ix {
+ self.transcript
+ .sender()
+ .send(TranscriptMsg::ScrollToCue(ix))
+ .unwrap();
}
}
- // if we are before the first subtitle, no need to look further
- if track.cues.is_empty() || position < track.cues.first().unwrap().start {
- cue.set(None);
- last_cue_ix.set(None);
+ self.primary_subtitle_state.reset();
+ }
+
+ fn update_secondary_subtitle_state(&mut self, position: gst::ClockTime) {
+ // sometimes we get a few position update messages after
+ // auto-pausing
+ if self.autopaused {
return;
}
- // otherwise, search the whole track (e.g. after seeking)
- match track
- .cues
- .iter()
- .enumerate()
- .rev()
- .find(|(_ix, cue)| cue.start <= position)
- {
- Some((ix, new_cue)) => {
- last_cue_ix.set(Some(ix));
- if position <= new_cue.end {
- cue.set(Some(new_cue.text.clone()));
- } else {
- cue.set(None);
- }
+ update_subtitle_state(&mut self.secondary_subtitle_state, position);
+
+ if self.secondary_subtitle_state.is_dirty() {
+ let cue = self.secondary_subtitle_state.active_cue();
+
+ self.subtitle_view
+ .sender()
+ .send(SubtitleViewMsg::SetSecondaryCue(cue))
+ .unwrap();
+ }
+
+ self.secondary_subtitle_state.reset();
+ }
+}
+
+fn update_subtitle_state(state: &mut SubtitleState, position: gst::ClockTime) {
+ if let Some(stream_ix) = state.stream_ix {
+ let lock = SUBTITLE_TRACKS.read();
+ let track = lock.get(&stream_ix).unwrap();
+
+ update_last_time_ix(&track.start_times, &mut state.last_started_cue_ix, position);
+ update_last_time_ix(&track.end_times, &mut state.last_ended_cue_ix, position);
+ }
+}
+
+fn update_last_time_ix(
+ times: &Vec<gst::ClockTime>,
+ last_time_ix: &mut Tracker<Option<usize>>,
+ current_time: gst::ClockTime,
+) {
+ // try to find index quickly (should succeed during normal playback)
+ if let Some(ix) = last_time_ix.get() {
+ let t0 = times.get(*ix).unwrap();
+ match (times.get(ix + 1), times.get(ix + 2)) {
+ (None, _) if current_time >= *t0 => {
+ return;
}
- None => {
- cue.set(None);
- last_cue_ix.set(None);
+ (Some(t1), _) if current_time >= *t0 && current_time < *t1 => {
+ return;
}
- };
+ (Some(t1), None) if current_time >= *t1 => {
+ last_time_ix.set(Some(ix + 1));
+ return;
+ }
+ (Some(t1), Some(t2)) if current_time >= *t1 && current_time < *t2 => {
+ last_time_ix.set(Some(ix + 1));
+ return;
+ }
+ _ => {}
+ }
+ }
+
+ // if we are before the first timestamp, no need to look further
+ if times.is_empty() || current_time < *times.first().unwrap() {
+ last_time_ix.set_if_ne(None);
+ return;
}
+
+ // otherwise, search the whole array (e.g. after seeking)
+ last_time_ix.set(times.iter().rposition(|time| *time <= current_time));
}
diff --git a/src/cue_view.rs b/src/cue_view.rs
index fbf2520..05c45c4 100644
--- a/src/cue_view.rs
+++ b/src/cue_view.rs
@@ -8,18 +8,25 @@ use relm4::prelude::*;
use relm4::{ComponentParts, SimpleComponent};
use unicode_segmentation::UnicodeSegmentation;
+use crate::subtitles::state::CueAddress;
+use crate::translation::TRANSLATIONS;
use crate::util::Tracker;
-pub struct CueView {
- text: Tracker<Option<String>>,
+pub struct ActiveCueViewState {
+ addr: CueAddress,
+ text: String,
// byte ranges for the words in `text`
word_ranges: Vec<Range<usize>>,
}
+pub struct CueView {
+ state: Tracker<Option<ActiveCueViewState>>,
+}
+
#[derive(Debug)]
pub enum CueViewMsg {
// messages from the app
- SetText(Option<String>),
+ SetCue(Option<CueAddress>),
// messages from UI
MouseMotion,
}
@@ -42,7 +49,7 @@ impl SimpleComponent for CueView {
gtk::Label {
add_controller: event_controller.clone(),
set_use_markup: true,
- set_visible: false,
+ set_sensitive: false,
set_justify: gtk::Justification::Center,
add_css_class: "cue-view",
},
@@ -71,8 +78,7 @@ impl SimpleComponent for CueView {
sender: relm4::ComponentSender<Self>,
) -> relm4::ComponentParts<Self> {
let model = Self {
- text: Tracker::new(None),
- word_ranges: Vec::new(),
+ state: Tracker::new(None),
};
let widgets = view_output!();
@@ -81,19 +87,26 @@ impl SimpleComponent for CueView {
}
fn update(&mut self, message: Self::Input, _sender: relm4::ComponentSender<Self>) {
- match message {
- CueViewMsg::SetText(text) => {
- self.text.set(text);
+ self.state.reset();
- if let Some(text) = self.text.get() {
- self.word_ranges = UnicodeSegmentation::unicode_word_indices(text.as_str())
+ match message {
+ CueViewMsg::SetCue(addr) => {
+ if let Some(addr) = addr {
+ let text = addr.resolve_text();
+ let word_ranges = UnicodeSegmentation::unicode_word_indices(text.as_str())
.map(|(offset, slice)| Range {
start: offset,
end: offset + slice.len(),
})
.collect();
+
+ self.state.set(Some(ActiveCueViewState {
+ addr,
+ text,
+ word_ranges,
+ }))
} else {
- self.word_ranges = Vec::new();
+ self.state.set(None);
}
}
CueViewMsg::MouseMotion => {
@@ -103,11 +116,16 @@ impl SimpleComponent for CueView {
}
fn post_view() {
- if self.text.is_dirty() {
- if let Some(text) = self.text.get() {
+ if self.state.is_dirty() {
+ if let Some(ActiveCueViewState {
+ addr: _,
+ text,
+ word_ranges,
+ }) = self.state.get()
+ {
let mut markup = String::new();
- let mut it = self.word_ranges.iter().enumerate().peekable();
+ let mut it = word_ranges.iter().enumerate().peekable();
if let Some((_, first_word_range)) = it.peek() {
markup.push_str(
glib::markup_escape_text(&text[..first_word_range.start]).as_str(),
@@ -127,24 +145,35 @@ impl SimpleComponent for CueView {
markup.push_str(glib::markup_escape_text(&text[next_gap_range]).as_str());
}
- widgets.label.set_markup(markup.as_str());
- widgets.label.set_visible(true);
+ widgets.label.set_markup(&markup);
+ widgets.label.set_sensitive(true);
} else {
- widgets.label.set_visible(false);
+ // insensitive = invisible by css
+ widgets.label.set_sensitive(false);
}
}
- if let Some(word_ix_str) = widgets.label.current_uri() {
- let range = self
- .word_ranges
- .get(usize::from_str(word_ix_str.as_str()).unwrap())
- .unwrap();
- widgets
- .popover_label
- .set_text(&self.text.get().as_ref().unwrap()[range.clone()]);
+ if let (
+ Some(ActiveCueViewState {
+ addr: CueAddress(stream_ix, cue_ix),
+ text: _,
+ word_ranges,
+ }),
+ Some(word_ix_str),
+ ) = (self.state.get(), widgets.label.current_uri())
+ {
+ let word_ix = usize::from_str(word_ix_str.as_str()).unwrap();
+
+ {
+ // TODO get translation
+ widgets.popover_label.set_text(word_ix_str.as_str());
+ }
+
+ let range = word_ranges.get(word_ix).unwrap();
widgets
.popover
.set_pointing_to(Some(&Self::get_rect_of_byte_range(&widgets.label, &range)));
+
widgets.popover.popup();
} else {
widgets.popover.popdown();
diff --git a/src/main.rs b/src/main.rs
index f010c6a..69ccb38 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,13 +2,14 @@ mod app;
mod cue_view;
mod open_dialog;
mod player;
-mod preferences;
-mod subtitle_extraction;
+mod preferences_dialog;
+mod settings;
mod subtitle_selection_dialog;
mod subtitle_view;
+mod subtitles;
mod track_selector;
-mod tracks;
mod transcript;
+mod translation;
mod util;
use gtk::{CssProvider, STYLE_PROVIDER_PRIORITY_APPLICATION, gdk, glib};
@@ -38,7 +39,7 @@ fn main() {
let css_provider = CssProvider::new();
css_provider.load_from_bytes(&glib::Bytes::from_static(include_bytes!(
- "../resources/style.css"
+ "../data/style.css"
)));
gtk::style_context_add_provider_for_display(
&gdk::Display::default().unwrap(),
diff --git a/src/open_dialog.rs b/src/open_dialog.rs
index 2f17c59..3b822be 100644
--- a/src/open_dialog.rs
+++ b/src/open_dialog.rs
@@ -5,10 +5,10 @@ use gtk::gio;
use gtk::glib::clone;
use relm4::prelude::*;
+use crate::subtitles::{MetadataCollection, StreamIndex, TrackMetadata};
use crate::track_selector::{
TrackInfo, TrackSelector, TrackSelectorInit, TrackSelectorMsg, TrackSelectorOutput,
};
-use crate::tracks::{StreamIndex, TrackMetadata};
use crate::util::Tracker;
pub struct OpenDialog {
@@ -23,6 +23,7 @@ pub struct OpenDialog {
whisper_stream_index: Option<StreamIndex>,
metadata_command_running: bool,
+ metadata: Option<MetadataCollection>,
}
#[derive(Debug)]
@@ -34,7 +35,7 @@ pub enum OpenDialogMsg {
FileSelected(gio::File),
UrlChanged(String),
SetDoWhisperExtraction(bool),
- WhisperTrackSelected(Option<StreamIndex>),
+ WhisperTrackSelected(StreamIndex),
Play,
}
@@ -42,6 +43,7 @@ pub enum OpenDialogMsg {
pub enum OpenDialogOutput {
Play {
url: String,
+ metadata: MetadataCollection,
whisper_stream_index: Option<StreamIndex>,
},
}
@@ -51,7 +53,7 @@ impl Component for OpenDialog {
type Init = adw::ApplicationWindow;
type Input = OpenDialogMsg;
type Output = OpenDialogOutput;
- type CommandOutput = Result<BTreeMap<StreamIndex, TrackMetadata>, ffmpeg::Error>;
+ type CommandOutput = Result<MetadataCollection, ffmpeg::Error>;
view! {
#[root]
@@ -186,6 +188,7 @@ impl Component for OpenDialog {
whisper_stream_index: None,
metadata_command_running: false,
+ metadata: None,
};
let widgets = view_output!();
@@ -227,23 +230,28 @@ impl Component for OpenDialog {
self.url.set(file.uri().into());
}
OpenDialogMsg::Play => {
- sender
- .output(OpenDialogOutput::Play {
- url: self.url.get().clone(),
- whisper_stream_index: if self.do_whisper_extraction {
- self.whisper_stream_index
- } else {
- None
- },
- })
- .unwrap();
- self.dialog.close();
+ if let Some(ref metadata) = self.metadata {
+ sender
+ .output(OpenDialogOutput::Play {
+ url: self.url.get().clone(),
+ metadata: metadata.clone(),
+ whisper_stream_index: if self.do_whisper_extraction {
+ self.whisper_stream_index
+ } else {
+ None
+ },
+ })
+ .unwrap();
+ self.dialog.close();
+ } else {
+ log::error!("metadata is unavailable, can't play");
+ }
}
OpenDialogMsg::SetDoWhisperExtraction(val) => {
self.do_whisper_extraction = val;
}
OpenDialogMsg::WhisperTrackSelected(track_index) => {
- self.whisper_stream_index = track_index;
+ self.whisper_stream_index = Some(track_index);
}
}
}
@@ -259,10 +267,10 @@ impl Component for OpenDialog {
self.metadata_command_running = false;
match message {
- Ok(audio_tracks) => {
+ Ok(metadata) => {
let list_model = gio::ListStore::new::<TrackInfo>();
- for (&stream_index, track) in audio_tracks.iter() {
+ for (&stream_index, track) in metadata.audio.iter() {
let track_info = TrackInfo::new(
stream_index,
track.language.map(|lang| lang.to_name()),
@@ -276,6 +284,8 @@ impl Component for OpenDialog {
.send(TrackSelectorMsg::SetListModel(list_model))
.unwrap();
+ self.metadata = Some(metadata);
+
self.next();
}
Err(e) => {
@@ -302,7 +312,7 @@ impl OpenDialog {
sender.spawn_oneshot_command(move || {
let input = ffmpeg::format::input(&url)?;
- let audio_tracks = input
+ let audio = input
.streams()
.filter_map(|stream| {
if stream.parameters().medium() == ffmpeg::media::Type::Audio {
@@ -312,8 +322,18 @@ impl OpenDialog {
}
})
.collect::<BTreeMap<_, _>>();
+ let subtitles = input
+ .streams()
+ .filter_map(|stream| {
+ if stream.parameters().medium() == ffmpeg::media::Type::Subtitle {
+ Some((stream.index(), TrackMetadata::from_ffmpeg_stream(&stream)))
+ } else {
+ None
+ }
+ })
+ .collect::<BTreeMap<_, _>>();
- Ok(audio_tracks)
+ Ok(MetadataCollection { audio, subtitles })
});
self.metadata_command_running = true;
diff --git a/src/preferences.rs b/src/preferences_dialog.rs
index c5f9bb1..5aacfe8 100644
--- a/src/preferences.rs
+++ b/src/preferences_dialog.rs
@@ -1,25 +1,23 @@
use adw::prelude::*;
-use gtk::gio;
use relm4::prelude::*;
-pub struct Preferences {
+use crate::settings::Settings;
+
+pub struct PreferencesDialog {
parent_window: adw::ApplicationWindow,
dialog: adw::PreferencesDialog,
}
#[derive(Debug)]
-pub enum PreferencesMsg {
+pub enum PreferencesDialogMsg {
Show,
}
-#[derive(Debug)]
-pub enum PreferencesOutput {}
-
#[relm4::component(pub)]
-impl SimpleComponent for Preferences {
+impl SimpleComponent for PreferencesDialog {
type Init = adw::ApplicationWindow;
- type Input = PreferencesMsg;
- type Output = PreferencesOutput;
+ type Input = PreferencesDialogMsg;
+ type Output = ();
view! {
#[root]
@@ -33,12 +31,9 @@ impl SimpleComponent for Preferences {
adw::PreferencesGroup {
set_title: "Machine Translation",
+ #[name(deepl_api_key_row)]
adw::EntryRow {
set_title: "DeepL API key",
- set_text: settings.string("deepl-api-key").as_str(),
- connect_changed[settings] => move |entry| {
- settings.set_string("deepl-api-key", entry.text().as_str()).unwrap()
- }
},
}
}
@@ -49,7 +44,7 @@ impl SimpleComponent for Preferences {
root: Self::Root,
_sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
- let settings = gio::Settings::new("tc.mal.lleap");
+ let settings = Settings::default();
let model = Self {
parent_window,
@@ -58,12 +53,16 @@ impl SimpleComponent for Preferences {
let widgets = view_output!();
+ settings
+ .bind_deepl_api_key(&widgets.deepl_api_key_row, "text")
+ .build();
+
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
match msg {
- PreferencesMsg::Show => {
+ PreferencesDialogMsg::Show => {
self.dialog.present(Some(&self.parent_window));
}
}
diff --git a/src/settings.rs b/src/settings.rs
new file mode 100644
index 0000000..eb1f6b9
--- /dev/null
+++ b/src/settings.rs
@@ -0,0 +1,5 @@
+use gsettings_macro::gen_settings;
+use gtk::{gio, glib};
+
+#[gen_settings(file = "./data/tc.mal.lleap.gschema.xml", id = "tc.mal.lleap")]
+pub struct Settings;
diff --git a/src/subtitle_selection_dialog.rs b/src/subtitle_selection_dialog.rs
index 6136d56..8e5d283 100644
--- a/src/subtitle_selection_dialog.rs
+++ b/src/subtitle_selection_dialog.rs
@@ -2,37 +2,47 @@ use adw::prelude::*;
use gtk::gio;
use relm4::prelude::*;
+use crate::subtitles::{MetadataCollection, StreamIndex};
use crate::track_selector::{
TrackInfo, TrackSelector, TrackSelectorInit, TrackSelectorMsg, TrackSelectorOutput,
};
-use crate::tracks::{SUBTITLE_TRACKS, StreamIndex};
+
+#[derive(Clone, Copy, Default, Debug)]
+pub struct SubtitleSettings {
+ pub primary_track_ix: Option<StreamIndex>,
+ pub secondary_track_ix: Option<StreamIndex>,
+ pub show_secondary: bool,
+ pub show_machine_translation: bool,
+}
pub struct SubtitleSelectionDialog {
parent_window: adw::ApplicationWindow,
dialog: adw::PreferencesDialog,
- track_list_model: gio::ListStore,
primary_selector: Controller<TrackSelector>,
secondary_selector: Controller<TrackSelector>,
- primary_track_ix: Option<StreamIndex>,
- secondary_track_ix: Option<StreamIndex>,
+
+ settings: SubtitleSettings,
}
#[derive(Debug)]
pub enum SubtitleSelectionDialogMsg {
Show,
- PrimaryTrackChanged(Option<StreamIndex>),
- SecondaryTrackChanged(Option<StreamIndex>),
+ Close,
+ // ui messages
+ PrimaryTrackChanged(StreamIndex),
+ SecondaryTrackChanged(StreamIndex),
+ ShowSecondaryChanged(bool),
+ ShowMachineTranslationChanged(bool),
}
#[derive(Debug)]
pub enum SubtitleSelectionDialogOutput {
- PrimaryTrackSelected(Option<StreamIndex>),
- SecondaryTrackSelected(Option<StreamIndex>),
+ ApplySubtitleSettings(SubtitleSettings),
}
#[relm4::component(pub)]
impl SimpleComponent for SubtitleSelectionDialog {
- type Init = adw::ApplicationWindow;
+ type Init = (adw::ApplicationWindow, MetadataCollection);
type Input = SubtitleSelectionDialogMsg;
type Output = SubtitleSelectionDialogOutput;
@@ -41,22 +51,50 @@ impl SimpleComponent for SubtitleSelectionDialog {
adw::PreferencesDialog {
set_title: "Subtitle Settings",
add: &page,
+ connect_closed => SubtitleSelectionDialogMsg::Close,
},
#[name(page)]
adw::PreferencesPage {
adw::PreferencesGroup {
model.primary_selector.widget(),
- model.secondary_selector.widget(),
+
+ adw::ExpanderRow {
+ set_title: "Show secondary subtitles",
+ set_subtitle: "Enable this if there exist subtitles a language you already know",
+ set_show_enable_switch: true,
+ #[watch]
+ set_enable_expansion: model.settings.show_secondary,
+ connect_enable_expansion_notify[sender] => move |expander_row| {
+ sender.input(SubtitleSelectionDialogMsg::ShowSecondaryChanged(expander_row.enables_expansion()))
+ },
+
+ add_row: model.secondary_selector.widget(),
+ },
+
+ adw::ExpanderRow {
+ set_title: "Show machine translations",
+ set_subtitle: "This is useful in case there are no subtitles in your native language or you prefer a more direct translation of the primary subtitles",
+ set_show_enable_switch: true,
+ #[watch]
+ set_enable_expansion: model.settings.show_machine_translation,
+ connect_enable_expansion_notify[sender] => move |expander_row| {
+ sender.input(SubtitleSelectionDialogMsg::ShowMachineTranslationChanged(expander_row.enables_expansion()))
+ },
+
+ // TODO add row for language selection
+ },
}
},
}
fn init(
- parent_window: Self::Init,
+ init: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
+ let (parent_window, metadata) = init;
+
let primary_selector = TrackSelector::builder()
.launch(TrackSelectorInit {
title: "Primary subtitle track",
@@ -81,73 +119,59 @@ impl SimpleComponent for SubtitleSelectionDialog {
let model = Self {
parent_window,
dialog: root.clone(),
- track_list_model: gio::ListStore::new::<TrackInfo>(),
primary_selector,
secondary_selector,
- primary_track_ix: None,
- secondary_track_ix: None,
+ settings: Default::default(),
};
let widgets = view_output!();
+ let track_list_model = gio::ListStore::new::<TrackInfo>();
+ for (&stream_index, track_metadata) in metadata.subtitles.iter() {
+ let track_info = TrackInfo::new(
+ stream_index,
+ track_metadata.language.map(|lang| lang.to_name()),
+ track_metadata.title.clone(),
+ );
+ track_list_model.append(&track_info);
+ }
+
+ model
+ .primary_selector
+ .sender()
+ .send(TrackSelectorMsg::SetListModel(track_list_model.clone()))
+ .unwrap();
+ model
+ .secondary_selector
+ .sender()
+ .send(TrackSelectorMsg::SetListModel(track_list_model.clone()))
+ .unwrap();
+
ComponentParts { model, widgets }
}
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
match msg {
SubtitleSelectionDialogMsg::Show => {
- self.update_track_list_model();
-
- self.primary_selector
- .sender()
- .send(TrackSelectorMsg::SetListModel(
- self.track_list_model.clone(),
- ))
- .unwrap();
- self.secondary_selector
- .sender()
- .send(TrackSelectorMsg::SetListModel(
- self.track_list_model.clone(),
- ))
- .unwrap();
-
self.dialog.present(Some(&self.parent_window));
}
- SubtitleSelectionDialogMsg::PrimaryTrackChanged(stream_index) => {
- self.primary_track_ix = stream_index;
- sender
- .output(SubtitleSelectionDialogOutput::PrimaryTrackSelected(
- stream_index,
- ))
- .unwrap();
+ SubtitleSelectionDialogMsg::Close => sender
+ .output(SubtitleSelectionDialogOutput::ApplySubtitleSettings(
+ self.settings,
+ ))
+ .unwrap(),
+ SubtitleSelectionDialogMsg::PrimaryTrackChanged(stream_ix) => {
+ self.settings.primary_track_ix = Some(stream_ix);
}
- SubtitleSelectionDialogMsg::SecondaryTrackChanged(stream_index) => {
- self.secondary_track_ix = stream_index;
- sender
- .output(SubtitleSelectionDialogOutput::SecondaryTrackSelected(
- stream_index,
- ))
- .unwrap();
+ SubtitleSelectionDialogMsg::SecondaryTrackChanged(stream_ix) => {
+ self.settings.secondary_track_ix = Some(stream_ix);
+ }
+ SubtitleSelectionDialogMsg::ShowSecondaryChanged(val) => {
+ self.settings.show_secondary = val;
+ }
+ SubtitleSelectionDialogMsg::ShowMachineTranslationChanged(val) => {
+ self.settings.show_machine_translation = val;
}
- }
- }
-}
-
-impl SubtitleSelectionDialog {
- fn update_track_list_model(&mut self) {
- let tracks = SUBTITLE_TRACKS.read();
-
- // Clear previous entries
- self.track_list_model.remove_all();
-
- // Add all available tracks
- for (&stream_index, track) in tracks.iter() {
- let track_info = TrackInfo::new(
- stream_index,
- track.metadata.language.map(|lang| lang.to_name()),
- track.metadata.title.clone(),
- );
- self.track_list_model.append(&track_info);
}
}
}
diff --git a/src/subtitle_view.rs b/src/subtitle_view.rs
index fd98c60..4de73dd 100644
--- a/src/subtitle_view.rs
+++ b/src/subtitle_view.rs
@@ -1,17 +1,22 @@
use crate::cue_view::{CueView, CueViewMsg, CueViewOutput};
-use crate::util::Tracker;
+use crate::subtitle_selection_dialog::SubtitleSettings;
+use crate::subtitles::state::CueAddress;
use gtk::prelude::*;
use relm4::prelude::*;
pub struct SubtitleView {
primary_cue: Controller<CueView>,
- secondary_cue: Tracker<Option<String>>,
+ secondary_cue: Option<String>,
+ machine_translation: Option<String>,
+ show_secondary: bool,
+ show_machine_translation: bool,
}
#[derive(Debug)]
pub enum SubtitleViewMsg {
- SetPrimaryCue(Option<String>),
- SetSecondaryCue(Option<String>),
+ SetPrimaryCue(Option<CueAddress>),
+ SetSecondaryCue(Option<CueAddress>),
+ ApplySubtitleSettings(SubtitleSettings),
}
#[derive(Debug)]
@@ -39,12 +44,30 @@ impl SimpleComponent for SubtitleView {
model.primary_cue.widget(),
gtk::Box {
+ #[watch]
+ set_visible: model.show_secondary,
set_vexpand: true,
},
gtk::Label {
- #[track = "model.secondary_cue.is_dirty()"]
- set_text: model.secondary_cue.get().as_ref().map(|val| val.as_str()).unwrap_or(""),
+ #[watch]
+ set_text: model.secondary_cue.as_ref().map(|val| val.as_str()).unwrap_or(""),
+ #[watch]
+ set_visible: model.show_secondary,
+ set_justify: gtk::Justification::Center,
+ },
+
+ gtk::Box {
+ #[watch]
+ set_visible: model.show_machine_translation,
+ set_vexpand: true,
+ },
+
+ gtk::Label {
+ #[watch]
+ set_text: model.machine_translation.as_ref().map(|val| val.as_str()).unwrap_or(""),
+ #[watch]
+ set_visible: model.show_machine_translation,
set_justify: gtk::Justification::Center,
},
@@ -67,7 +90,10 @@ impl SimpleComponent for SubtitleView {
CueViewOutput::MouseEnter => SubtitleViewOutput::SetHoveringCue(true),
CueViewOutput::MouseLeave => SubtitleViewOutput::SetHoveringCue(false),
}),
- secondary_cue: Tracker::new(None),
+ secondary_cue: None,
+ machine_translation: None,
+ show_secondary: false,
+ show_machine_translation: false,
};
let widgets = view_output!();
@@ -76,18 +102,21 @@ impl SimpleComponent for SubtitleView {
}
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
- // Reset trackers
- self.secondary_cue.reset();
-
match msg {
- SubtitleViewMsg::SetPrimaryCue(value) => {
+ SubtitleViewMsg::SetPrimaryCue(addr) => {
self.primary_cue
.sender()
- .send(CueViewMsg::SetText(value))
+ .send(CueViewMsg::SetCue(addr))
.unwrap();
+ self.machine_translation = addr.and_then(|a| a.resolve_translation())
+ }
+ SubtitleViewMsg::SetSecondaryCue(addr) => {
+ let text = addr.map(|addr| addr.resolve_text());
+ self.secondary_cue = text;
}
- SubtitleViewMsg::SetSecondaryCue(value) => {
- self.secondary_cue.set(value);
+ SubtitleViewMsg::ApplySubtitleSettings(settings) => {
+ self.show_secondary = settings.show_secondary;
+ self.show_machine_translation = settings.show_machine_translation;
}
}
}
diff --git a/src/subtitle_extraction/embedded.rs b/src/subtitles/extraction/embedded.rs
index 0ba6178..920f52b 100644
--- a/src/subtitle_extraction/embedded.rs
+++ b/src/subtitles/extraction/embedded.rs
@@ -2,7 +2,7 @@ use std::sync::mpsc;
use anyhow::Context;
-use crate::subtitle_extraction::*;
+use crate::{subtitles::SubtitleCue, subtitles::extraction::*};
pub fn extract_embedded_subtitles(
// stream index to use when storing extracted subtitles, this index already
@@ -23,12 +23,6 @@ pub fn extract_embedded_subtitles(
match decoder.decode(&packet, &mut subtitle) {
Ok(true) => {
if let Some(cue) = parse_subtitle(&subtitle, &packet, time_base) {
- SUBTITLE_TRACKS
- .write()
- .get_mut(&stream_ix)
- .unwrap()
- .cues
- .push(cue.clone());
sender
.output(SubtitleExtractorOutput::NewCue(stream_ix, cue))
.unwrap();
@@ -72,10 +66,14 @@ fn parse_subtitle(
.collect::<Vec<String>>()
.join("\n— ");
- let start = pts_to_clock_time(packet.pts()?);
- let end = pts_to_clock_time(packet.pts()? + packet.duration());
+ let start_time = pts_to_clock_time(packet.pts()?);
+ let end_time = pts_to_clock_time(packet.pts()? + packet.duration());
- Some(SubtitleCue { start, end, text })
+ Some(SubtitleCue {
+ text,
+ start_time,
+ end_time,
+ })
}
fn extract_dialogue_text(dialogue_line: &str) -> Option<String> {
diff --git a/src/subtitle_extraction/mod.rs b/src/subtitles/extraction/mod.rs
index 9e7fff4..b012658 100644
--- a/src/subtitle_extraction/mod.rs
+++ b/src/subtitles/extraction/mod.rs
@@ -8,7 +8,7 @@ use std::{collections::BTreeMap, sync::mpsc, thread};
use ffmpeg::Rational;
use relm4::{ComponentSender, Worker};
-use crate::tracks::{SUBTITLE_TRACKS, StreamIndex, SubtitleCue, SubtitleTrack, TrackMetadata};
+use crate::subtitles::{SUBTITLE_TRACKS, StreamIndex, SubtitleCue, SubtitleTrack, TrackMetadata};
pub struct SubtitleExtractor {}
@@ -87,10 +87,7 @@ impl SubtitleExtractor {
if stream.parameters().medium() == ffmpeg::media::Type::Subtitle {
let metadata = TrackMetadata::from_ffmpeg_stream(&stream);
- let track = SubtitleTrack {
- metadata,
- cues: Vec::new(),
- };
+ let track = SubtitleTrack::new(metadata);
SUBTITLE_TRACKS.write().insert(stream_ix, track);
@@ -117,10 +114,7 @@ impl SubtitleExtractor {
None => "Auto-generated from audio (Whisper)".to_string(),
});
- let track = SubtitleTrack {
- metadata,
- cues: Vec::new(),
- };
+ let track = SubtitleTrack::new(metadata);
SUBTITLE_TRACKS.write().insert(stream_ix, track);
diff --git a/src/subtitle_extraction/whisper.rs b/src/subtitles/extraction/whisper.rs
index ffa2e47..bd6fba7 100644
--- a/src/subtitle_extraction/whisper.rs
+++ b/src/subtitles/extraction/whisper.rs
@@ -8,7 +8,10 @@ use anyhow::Context;
use ffmpeg::{filter, frame};
use serde::Deserialize;
-use crate::{subtitle_extraction::*, tracks::StreamIndex};
+use crate::{
+ subtitles::extraction::*,
+ subtitles::{StreamIndex, SubtitleCue},
+};
#[derive(Debug, Deserialize)]
struct WhisperCue {
@@ -117,18 +120,11 @@ fn handle_packet(
let whisper_cue: WhisperCue = serde_json::from_str(&line_buf)?;
let cue = SubtitleCue {
- start: gst::ClockTime::from_mseconds(whisper_cue.start),
- end: gst::ClockTime::from_mseconds(whisper_cue.end),
text: whisper_cue.text,
+ start_time: gst::ClockTime::from_mseconds(whisper_cue.start),
+ end_time: gst::ClockTime::from_mseconds(whisper_cue.end),
};
- // TODO deduplicate this vs. the code in embedded.rs
- SUBTITLE_TRACKS
- .write()
- .get_mut(&stream_ix)
- .unwrap()
- .cues
- .push(cue.clone());
sender
.output(SubtitleExtractorOutput::NewCue(stream_ix, cue))
.unwrap();
diff --git a/src/subtitles/mod.rs b/src/subtitles/mod.rs
new file mode 100644
index 0000000..a545d52
--- /dev/null
+++ b/src/subtitles/mod.rs
@@ -0,0 +1,86 @@
+pub mod extraction;
+pub mod state;
+
+use std::collections::BTreeMap;
+
+use relm4::SharedState;
+
+pub type StreamIndex = usize;
+
+#[derive(Debug, Clone)]
+pub struct MetadataCollection {
+ pub audio: BTreeMap<StreamIndex, TrackMetadata>,
+ pub subtitles: BTreeMap<StreamIndex, TrackMetadata>,
+}
+
+#[derive(Debug, Clone)]
+pub struct TrackMetadata {
+ pub language: Option<isolang::Language>,
+ pub title: Option<String>,
+}
+
+#[derive(Debug, Clone)]
+pub struct SubtitleCue {
+ pub text: String,
+ pub start_time: gst::ClockTime,
+ pub end_time: gst::ClockTime,
+}
+
+#[derive(Debug, Clone)]
+pub struct SubtitleTrack {
+ pub metadata: TrackMetadata,
+ // SoA of cue text, start timestamp, end timestamp
+ pub texts: Vec<String>,
+ pub start_times: Vec<gst::ClockTime>,
+ pub end_times: Vec<gst::ClockTime>,
+}
+
+pub static SUBTITLE_TRACKS: SharedState<BTreeMap<StreamIndex, SubtitleTrack>> = SharedState::new();
+
+impl TrackMetadata {
+ pub fn from_ffmpeg_stream(stream: &ffmpeg::Stream) -> Self {
+ let language_code = stream.metadata().get("language").map(|s| s.to_string());
+ let title = stream.metadata().get("title").map(|s| s.to_string());
+
+ Self {
+ language: language_code.and_then(|code| isolang::Language::from_639_2b(&code)),
+ title,
+ }
+ }
+}
+
+impl SubtitleTrack {
+ pub fn new(metadata: TrackMetadata) -> Self {
+ Self {
+ metadata,
+ texts: Vec::new(),
+ start_times: Vec::new(),
+ end_times: Vec::new(),
+ }
+ }
+
+ pub fn push_cue(&mut self, cue: SubtitleCue) {
+ let SubtitleCue {
+ text,
+ start_time,
+ end_time,
+ } = cue;
+
+ self.texts.push(text);
+ self.start_times.push(start_time);
+ self.end_times.push(end_time);
+ }
+
+ pub fn iter_cloned_cues(&self) -> impl Iterator<Item = SubtitleCue> {
+ self.texts
+ .iter()
+ .cloned()
+ .zip(self.start_times.iter().cloned())
+ .zip(self.end_times.iter().cloned())
+ .map(|((text, start_time), end_time)| SubtitleCue {
+ text,
+ start_time,
+ end_time,
+ })
+ }
+}
diff --git a/src/subtitles/state.rs b/src/subtitles/state.rs
new file mode 100644
index 0000000..6b1ebda
--- /dev/null
+++ b/src/subtitles/state.rs
@@ -0,0 +1,63 @@
+use crate::{
+ subtitles::{SUBTITLE_TRACKS, StreamIndex},
+ translation::TRANSLATIONS,
+ util::Tracker,
+};
+
+#[derive(Default)]
+pub struct SubtitleState {
+ pub stream_ix: Option<StreamIndex>,
+ pub last_started_cue_ix: Tracker<Option<usize>>,
+ pub last_ended_cue_ix: Tracker<Option<usize>>,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct CueAddress(pub StreamIndex, pub usize);
+
+impl SubtitleState {
+ pub fn active_cue(&self) -> Option<CueAddress> {
+ if let Some(stream_ix) = self.stream_ix {
+ match (*self.last_started_cue_ix, *self.last_ended_cue_ix) {
+ (None, _) => None,
+ (Some(started_ix), None) => Some(CueAddress(stream_ix, started_ix)),
+ (Some(started_ix), Some(ended_ix)) => {
+ if started_ix > ended_ix {
+ Some(CueAddress(stream_ix, started_ix))
+ } else {
+ None
+ }
+ }
+ }
+ } else {
+ None
+ }
+ }
+
+ pub fn is_dirty(&self) -> bool {
+ self.last_started_cue_ix.is_dirty() || self.last_ended_cue_ix.is_dirty()
+ }
+
+ pub fn reset(&mut self) {
+ self.last_started_cue_ix.reset();
+ self.last_ended_cue_ix.reset();
+ }
+
+ pub fn set_stream_ix(&mut self, stream_ix: Option<StreamIndex>) {
+ self.stream_ix = stream_ix;
+ self.last_started_cue_ix.set(None);
+ self.last_ended_cue_ix.set(None);
+ }
+}
+
+impl CueAddress {
+ pub fn resolve_text(&self) -> String {
+ SUBTITLE_TRACKS.read().get(&self.0).unwrap().texts[self.1].clone()
+ }
+
+ pub fn resolve_translation(&self) -> Option<String> {
+ TRANSLATIONS
+ .read()
+ .get(&self.0)
+ .and_then(|tln| tln.get(self.1).cloned())
+ }
+}
diff --git a/src/track_selector.rs b/src/track_selector.rs
index 5c56e4d..ce04d07 100644
--- a/src/track_selector.rs
+++ b/src/track_selector.rs
@@ -2,7 +2,7 @@ use adw::prelude::*;
use gtk::{gio, glib};
use relm4::prelude::*;
-use crate::tracks::StreamIndex;
+use crate::{subtitles::StreamIndex, util::Tracker};
glib::wrapper! {
pub struct TrackInfo(ObjectSubclass<imp::TrackInfo>);
@@ -65,11 +65,12 @@ pub struct TrackSelectorInit {
#[derive(Debug)]
pub enum TrackSelectorMsg {
SetListModel(gio::ListStore),
+ Changed(StreamIndex),
}
#[derive(Debug)]
pub enum TrackSelectorOutput {
- Changed(Option<StreamIndex>),
+ Changed(StreamIndex),
}
#[relm4::component(pub)]
@@ -87,11 +88,15 @@ impl SimpleComponent for TrackSelector {
set_factory: Some(&track_factory),
#[watch]
set_model: Some(&model.track_list_model),
- #[watch]
- set_selected: model.track_ix.map_or(gtk::INVALID_LIST_POSITION, |ix| get_list_ix_from_stream_ix(&model.track_list_model, ix)),
+ // #[watch]
+ // set_selected: model.track_ix.map_or(gtk::INVALID_LIST_POSITION, |ix| get_list_ix_from_stream_ix(&model.track_list_model, ix)),
connect_selected_notify[sender] => move |combo| {
- let stream_index = get_stream_ix_from_combo(combo);
- sender.output(TrackSelectorOutput::Changed(stream_index)).unwrap();
+ if let Some(stream_ix) = get_stream_ix_from_combo(combo) {
+ println!("selected {}", stream_ix);
+ sender.input(TrackSelectorMsg::Changed(stream_ix));
+ } else {
+ println!("selected none");
+ }
},
},
@@ -155,11 +160,18 @@ impl SimpleComponent for TrackSelector {
ComponentParts { model, widgets }
}
- fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
+ fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
match msg {
TrackSelectorMsg::SetListModel(list_model) => {
self.track_list_model = list_model;
}
+ TrackSelectorMsg::Changed(track_ix) => {
+ println!("changed {:?}", track_ix);
+ self.track_ix = Some(track_ix);
+ sender
+ .output(TrackSelectorOutput::Changed(track_ix))
+ .unwrap();
+ }
}
}
}
diff --git a/src/tracks.rs b/src/tracks.rs
deleted file mode 100644
index 4d69e12..0000000
--- a/src/tracks.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use std::collections::BTreeMap;
-
-use relm4::SharedState;
-
-pub type StreamIndex = usize;
-
-#[derive(Debug, Clone)]
-pub struct TrackMetadata {
- pub language: Option<isolang::Language>,
- pub title: Option<String>,
-}
-
-#[derive(Debug, Clone)]
-pub struct SubtitleTrack {
- pub metadata: TrackMetadata,
- pub cues: Vec<SubtitleCue>,
-}
-
-#[derive(Debug, Clone)]
-pub struct SubtitleCue {
- pub start: gst::ClockTime,
- pub end: gst::ClockTime,
- pub text: String,
-}
-
-pub static SUBTITLE_TRACKS: SharedState<BTreeMap<StreamIndex, SubtitleTrack>> = SharedState::new();
-
-impl TrackMetadata {
- pub fn from_ffmpeg_stream(stream: &ffmpeg::Stream) -> Self {
- let language_code = stream.metadata().get("language").map(|s| s.to_string());
- let title = stream.metadata().get("title").map(|s| s.to_string());
-
- Self {
- language: language_code.and_then(|code| isolang::Language::from_639_2b(&code)),
- title,
- }
- }
-}
diff --git a/src/transcript.rs b/src/transcript.rs
index a8ae554..602e340 100644
--- a/src/transcript.rs
+++ b/src/transcript.rs
@@ -1,7 +1,7 @@
use gtk::{ListBox, pango::WrapMode, prelude::*};
use relm4::prelude::*;
-use crate::tracks::{SUBTITLE_TRACKS, StreamIndex, SubtitleCue};
+use crate::subtitles::{SUBTITLE_TRACKS, StreamIndex, SubtitleCue};
#[derive(Debug)]
pub enum SubtitleCueOutput {
@@ -20,7 +20,7 @@ impl FactoryComponent for SubtitleCue {
gtk::Button {
inline_css: "padding: 5px; border-radius: 0;",
connect_clicked: {
- let start = self.start;
+ let start = self.start_time;
move |_| {
sender.output(SubtitleCueOutput::SeekTo(start)).unwrap()
}
@@ -124,8 +124,8 @@ impl SimpleComponent for Transcript {
if let Some(stream_ix) = stream_index {
let tracks = SUBTITLE_TRACKS.read();
if let Some(track) = tracks.get(&stream_ix) {
- for cue in &track.cues {
- self.active_cues.guard().push_back(cue.clone());
+ for cue in track.iter_cloned_cues() {
+ self.active_cues.guard().push_back(cue);
}
}
}
diff --git a/src/translation/deepl.rs b/src/translation/deepl.rs
new file mode 100644
index 0000000..f2e84d7
--- /dev/null
+++ b/src/translation/deepl.rs
@@ -0,0 +1,106 @@
+use std::{collections::BTreeMap, time::Duration};
+
+use deepl::DeepLApi;
+use relm4::prelude::*;
+
+use crate::{
+ settings::Settings,
+ subtitles::{SUBTITLE_TRACKS, StreamIndex},
+ translation::TRANSLATIONS,
+};
+
+pub struct DeeplTranslator {
+ stream_ix: Option<StreamIndex>,
+ next_cues_to_translate: BTreeMap<StreamIndex, usize>,
+}
+
+#[derive(Debug)]
+pub enum DeeplTranslatorMsg {
+ SelectTrack(Option<StreamIndex>),
+ // this is only used to drive the async translation
+ DoTranslate,
+}
+
+impl AsyncComponent for DeeplTranslator {
+ type Init = ();
+ type Input = DeeplTranslatorMsg;
+ type Output = ();
+ type CommandOutput = ();
+ type Root = ();
+ type Widgets = ();
+
+ async fn init(
+ _init: Self::Init,
+ _root: Self::Root,
+ sender: relm4::AsyncComponentSender<Self>,
+ ) -> AsyncComponentParts<Self> {
+ let model = Self {
+ stream_ix: None,
+ next_cues_to_translate: BTreeMap::new(),
+ };
+
+ sender.input(DeeplTranslatorMsg::DoTranslate);
+
+ AsyncComponentParts { model, widgets: () }
+ }
+
+ async fn update(
+ &mut self,
+ message: Self::Input,
+ sender: AsyncComponentSender<Self>,
+ _root: &Self::Root,
+ ) {
+ match message {
+ DeeplTranslatorMsg::SelectTrack(stream_ix) => {
+ self.stream_ix = stream_ix;
+ }
+ DeeplTranslatorMsg::DoTranslate => self.do_translate(sender).await,
+ }
+ }
+
+ fn init_root() -> Self::Root {
+ ()
+ }
+}
+
+impl DeeplTranslator {
+ async fn do_translate(&mut self, sender: AsyncComponentSender<Self>) {
+ if let Some(stream_ix) = self.stream_ix {
+ let deepl = DeepLApi::with(&Settings::default().deepl_api_key()).new();
+
+ let next_cue_to_translate = self.next_cues_to_translate.entry(stream_ix).or_insert(0);
+
+ if let Some(cue) = {
+ SUBTITLE_TRACKS
+ .read()
+ .get(&stream_ix)
+ .unwrap()
+ .texts
+ .get(*next_cue_to_translate)
+ .cloned()
+ } {
+ match deepl
+ .translate_text(cue, deepl::Lang::EN)
+ .model_type(deepl::ModelType::PreferQualityOptimized)
+ .await
+ {
+ Ok(mut resp) => {
+ TRANSLATIONS
+ .write()
+ .entry(stream_ix)
+ .or_insert(Vec::new())
+ .push(resp.translations.pop().unwrap().text);
+
+ *next_cue_to_translate = *next_cue_to_translate + 1;
+ }
+ Err(e) => {
+ log::error!("error fetching translation: {}", e)
+ }
+ };
+ }
+ }
+
+ relm4::tokio::time::sleep(Duration::from_secs(1)).await;
+ sender.input(DeeplTranslatorMsg::DoTranslate);
+ }
+}
diff --git a/src/translation/mod.rs b/src/translation/mod.rs
new file mode 100644
index 0000000..4a1b358
--- /dev/null
+++ b/src/translation/mod.rs
@@ -0,0 +1,11 @@
+use std::collections::BTreeMap;
+
+use relm4::SharedState;
+
+use crate::subtitles::StreamIndex;
+
+pub mod deepl;
+
+pub use deepl::DeeplTranslator;
+
+pub static TRANSLATIONS: SharedState<BTreeMap<StreamIndex, Vec<String>>> = SharedState::new();
diff --git a/src/util/tracker.rs b/src/util/tracker.rs
index 69a1c5f..060acae 100644
--- a/src/util/tracker.rs
+++ b/src/util/tracker.rs
@@ -1,3 +1,5 @@
+use std::ops::Deref;
+
pub struct Tracker<T> {
inner: T,
dirty: bool,
@@ -40,8 +42,24 @@ impl<T> Tracker<T> {
}
}
+impl<T> Deref for Tracker<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.get()
+ }
+}
+
impl<T: Default> Default for Tracker<T> {
fn default() -> Self {
Self::new(T::default())
}
}
+
+impl<T: Eq> Tracker<T> {
+ pub fn set_if_ne(&mut self, value: T) {
+ if self.inner != value {
+ self.set(value);
+ }
+ }
+}
diff --git a/subs.srt b/subs.srt
deleted file mode 100644
index 5442ab9..0000000
--- a/subs.srt
+++ /dev/null
@@ -1,644 +0,0 @@
-0
-00:00:00.000 --> 00:00:00.040
-...
-
-0
-00:00:00.040 --> 00:00:13.000
-Alors, comment ça s'est passé ?
-
-0
-00:00:13.000 --> 00:00:17.180
-Formidable.
-
-0
-00:00:17.180 --> 00:00:23.000
-Vous voulez pas en parler ?
-
-0
-00:00:23.000 --> 00:00:25.040
-Si, je veux en parler.
-
-1
-00:00:29.994 --> 00:00:37.894
-Ça s'est passé quand ?
-
-1
-00:00:37.894 --> 00:00:38.414
-Ce matin.
-
-1
-00:00:38.414 --> 00:00:56.054
-Elle a pris ça comment ?
-
-1
-00:00:56.054 --> 00:00:58.814
-À votre avis ?
-
-1
-00:00:58.814 --> 00:00:59.974
-Et vous ?
-
-2
-00:00:59.988 --> 00:01:02.428
-ou à une vraie partie de plaisir.
-
-2
-00:01:02.428 --> 00:01:08.748
-Ça va ?
-
-2
-00:01:08.748 --> 00:01:09.428
-Je pars.
-
-2
-00:01:09.428 --> 00:01:12.788
-Tu pars où ?
-
-2
-00:01:12.788 --> 00:01:13.388
-En Jordanie.
-
-2
-00:01:13.388 --> 00:01:17.128
-Pour quoi faire ?
-
-2
-00:01:17.128 --> 00:01:19.488
-J'ai accepté un poste au lycée français d'Aman.
-
-2
-00:01:19.488 --> 00:01:23.368
-Mais longtemps ?
-
-2
-00:01:23.368 --> 00:01:24.688
-Ça remplace moi, je sais pas combien de temps.
-
-3
-00:01:29.983 --> 00:01:42.983
-Paul, tu es en train de me dire que tu pars ou tu es en train de me dire autre chose ?
-
-3
-00:01:42.983 --> 00:01:47.223
-Les deux.
-
-3
-00:01:47.223 --> 00:01:51.423
-Vous avez adopté quelle stratégie finalement ?
-
-3
-00:01:51.423 --> 00:01:51.983
-La plus nulle.
-
-3
-00:01:51.983 --> 00:01:54.023
-C'est une nuit à pas de stratégie.
-
-3
-00:01:54.023 --> 00:01:58.323
-Celle qui est naze, triste, juste on s'arrête quoi.
-
-4
-00:01:59.978 --> 00:02:03.938
-Vous avez dit quoi ?
-
-4
-00:02:03.938 --> 00:02:05.138
-Les trucs les plus nuls de la Terre.
-
-4
-00:02:05.138 --> 00:02:08.358
-Que je ne parvenais plus à comprendre ce que je faisais à Damas.
-
-4
-00:02:08.358 --> 00:02:10.978
-Que je vivais mal à mon divorce.
-
-4
-00:02:10.978 --> 00:02:14.398
-Que je ne supportais pas de vieillir, d'être prof.
-
-4
-00:02:14.398 --> 00:02:16.678
-Que ma fille me manquait.
-
-4
-00:02:16.678 --> 00:02:20.438
-Bref, qu'il fallait que je parte et que je devais partir seul.
-
-4
-00:02:20.438 --> 00:02:27.358
-Et qu'est-ce qu'elle a dit, elle ?
-
-4
-00:02:27.358 --> 00:02:29.358
-Qu'est-ce que vous voulez qu'elle dise ?
-
-5
-00:02:29.972 --> 00:02:31.772
-Elle m'a insulté.
-
-5
-00:02:31.772 --> 00:02:36.572
-Ça ne l'est pas.
-
-5
-00:02:36.572 --> 00:02:36.952
-Elle a rêvé.
-
-5
-00:02:36.952 --> 00:02:39.032
-Elle a un peu pleuré.
-
-5
-00:02:39.032 --> 00:02:47.412
-Je savais que tu n'allais pas rester éternellement en Syrie, surtout en ce moment.
-
-5
-00:02:47.412 --> 00:02:50.472
-Enfin, la scène habituelle, quoi. Je la largue un peu brutalement quand même.
-
-5
-00:02:50.472 --> 00:02:52.132
-Ça ne me dérangeait pas.
-
-5
-00:02:52.132 --> 00:02:56.232
-Je crois même que ça m'arrangeait à cause de Marwan.
-
-5
-00:02:56.232 --> 00:02:57.732
-Elle m'a crié dessus.
-
-6
-00:02:59.967 --> 00:03:02.407
-C'est triste.
-
-6
-00:03:02.407 --> 00:03:09.567
-Moi aussi, je suis triste.
-
-6
-00:03:09.567 --> 00:03:13.187
-Je t'aime bien.
-
-6
-00:03:13.187 --> 00:03:17.307
-C'est très bien.
-
-6
-00:03:17.307 --> 00:03:22.027
-Et puis c'est bien que tu me dises ça comme ça.
-
-6
-00:03:22.027 --> 00:03:25.047
-J'aurais pas aimé le savoir trop à l'avance.
-
-7
-00:03:29.962 --> 00:03:32.002
-Elle a voulu me jeter son sac au visage, mais elle s'est retenue.
-
-7
-00:03:32.002 --> 00:03:33.862
-Elle est partie.
-
-7
-00:03:33.862 --> 00:03:38.002
-En claquant la porte ?
-
-7
-00:03:38.002 --> 00:03:39.222
-En claquant la porte.
-
-7
-00:03:39.222 --> 00:03:47.862
-Bon, il va être temps que je quitte le pays, moi.
-
-7
-00:03:47.862 --> 00:03:51.242
-Il n'y a pas trop le choix, hein ?
-
-7
-00:03:51.242 --> 00:03:52.282
-Non.
-
-7
-00:03:52.282 --> 00:03:56.262
-Bon, je vais couper.
-
-7
-00:03:56.262 --> 00:03:57.222
-Ok, à bientôt.
-
-7
-00:03:57.222 --> 00:03:58.062
-À bientôt.
-
-8
-00:03:59.956 --> 00:04:09.956
-♪♪
-
-8
-00:04:09.956 --> 00:04:19.956
-♪♪
-
-8
-00:04:19.956 --> 00:04:29.956
-♪♪
-
-9
-00:04:29.951 --> 00:04:43.131
-Vous allez avec eux, moi je vous suivrai derrière.
-
-9
-00:04:43.131 --> 00:04:47.991
-Bonjour, on m'appelle Pépé.
-
-9
-00:04:47.991 --> 00:04:50.011
-Et moi c'est Mémé.
-
-9
-00:04:50.011 --> 00:04:50.691
-Enchanté.
-
-9
-00:04:50.691 --> 00:04:52.211
-Derrière c'est la mule.
-
-10
-00:04:59.946 --> 00:05:22.606
-C'est quoi le programme ?
-
-10
-00:05:22.606 --> 00:05:24.766
-On vous conduit à votre nouveau chez vous, on va passer par le SASS.
-
-10
-00:05:24.766 --> 00:05:26.666
-Celui de l'étoile ?
-
-10
-00:05:26.666 --> 00:05:27.966
-Non, celui d'opéra.
-
-12
-00:06:29.295 --> 00:06:29.935
-Bon à partir de là,
-
-13
-00:06:29.930 --> 00:06:31.930
-A partir de maintenant, vous passez toujours par un sas.
-
-13
-00:06:31.930 --> 00:06:32.930
-D'accord ?
-
-13
-00:06:32.930 --> 00:06:34.930
-Quelle que soit votre destination, vous passez par un sas.
-
-13
-00:06:34.930 --> 00:06:39.930
-Et si vous allez boulevard Mortier ou si vous en revenez, vous passez par un sas et vous changez de moyen de locomotion.
-
-13
-00:06:39.930 --> 00:06:40.930
-D'accord ?
-
-13
-00:06:40.930 --> 00:06:43.930
-Si vous venez à pied, vous prenez une voiture et vice versa.
-
-13
-00:06:43.930 --> 00:06:46.930
-D'accord.
-
-14
-00:06:59.924 --> 00:07:04.924
-I'm going to make a fire.
-
-14
-00:07:04.924 --> 00:07:09.924
-I'll make a fire with a fire extinguisher.
-
-14
-00:07:09.924 --> 00:07:14.924
-I'll make a fire with a fire extinguisher.
-
-14
-00:07:14.924 --> 00:07:19.924
-I'll make a fire with a fire extinguisher.
-
-14
-00:07:19.924 --> 00:07:24.924
-I'll make a fire with a fire extinguisher.
-
-14
-00:07:24.924 --> 00:07:29.884
-Takk for watching!
-
-15
-00:07:29.919 --> 00:07:43.919
-موسيقى
-
-15
-00:07:43.919 --> 00:07:57.919
-موسيقى
-
-17
-00:08:29.908 --> 00:08:40.308
-Il conduisait à deux à l'heure, il paraît.
-
-17
-00:08:40.308 --> 00:08:42.768
-Il était complètement ivre.
-
-17
-00:08:42.768 --> 00:08:45.748
-Il aurait dit aux policiers qu'il allait lentement parce qu'il avait peur d'avoir un incident.
-
-17
-00:08:45.748 --> 00:08:47.288
-Ils l'ont amené au poste.
-
-17
-00:08:47.288 --> 00:08:50.888
-Là, c'est là qu'a eu lieu l'interpellation, à Belouisdad.
-
-17
-00:08:50.888 --> 00:08:53.248
-Au centre d'Alger, tout près du Sofitel.
-
-17
-00:08:53.248 --> 00:08:58.368
-Ça, c'est la reconstitution du trajet à partir des points où le téléphone a borné.
-
-17
-00:08:59.468 --> 00:08:59.868
-Merci.
-
-18
-00:08:59.903 --> 00:09:00.763
-J'ai suivi cet itinéraire.
-
-18
-00:09:00.763 --> 00:09:07.283
-Là, c'est le commissariat de police de Belouisdad.
-
-18
-00:09:07.283 --> 00:09:08.903
-Et depuis ?
-
-18
-00:09:08.903 --> 00:09:10.543
-Depuis, il n'a pas bougé.
-
-18
-00:09:10.543 --> 00:09:12.623
-On vérifie tous les quarts d'heure.
-
-18
-00:09:12.623 --> 00:09:14.663
-Cyclone est toujours à l'intérieur.
-
-18
-00:09:14.663 --> 00:09:16.723
-On a mis une équipe devant l'entrée.
-
-18
-00:09:16.723 --> 00:09:18.903
-Vous êtes sûr que Cyclone était bourré ?
-
-18
-00:09:18.903 --> 00:09:20.123
-Ouais.
-
-18
-00:09:20.123 --> 00:09:22.163
-Mais il y avait pas mal de témoins.
-
-18
-00:09:22.163 --> 00:09:25.903
-Il était musulman pratiquant ?
-
-18
-00:09:25.903 --> 00:09:26.863
-Ouais.
-
-19
-00:09:29.898 --> 00:09:35.058
-Je propose qu'on envoie quelqu'un au commissariat pour vérifier dans quel état il est.
-
-19
-00:09:35.058 --> 00:09:37.178
-Très bien. Et dès qu'il sort, vous me promenez.
-
-19
-00:09:37.178 --> 00:09:45.938
-Dites-moi, vous l'aviez entraîné à subir un interrogatoire sous alcool, je suppose ?
-
-19
-00:09:45.938 --> 00:09:46.338
-Bien sûr.
-
-19
-00:09:46.338 --> 00:09:50.038
-Je peux voir les enregistrements ? J'aimerais bien savoir comment il s'en était sorti.
-
-19
-00:09:50.038 --> 00:09:53.678
-Il s'en était bien sorti ?
-
-19
-00:09:53.678 --> 00:09:55.578
-Ouais. Je vous les trouve.
-
-20
-00:09:59.892 --> 00:10:02.892
-Vous avez besoin de quelque chose, monsieur Manon ?
-
-20
-00:10:02.892 --> 00:10:04.892
-Non, non, ça va, ça va.
-
-21
-00:10:29.887 --> 00:10:37.367
-Il vient d'arriver.
-
-21
-00:10:37.367 --> 00:10:39.447
-C'est une rôle, ça.
-
-21
-00:10:39.447 --> 00:10:43.607
-J'ai déjà essayé chez le DG, ça doit te coûter un peu du cul.
-
-21
-00:10:43.607 --> 00:10:45.207
-Tu peux le dire, ouais.
-
-21
-00:10:45.207 --> 00:10:47.267
-Ça, c'est fini le dos coincé.
-
-21
-00:10:47.267 --> 00:10:49.647
-Tu veux que je t'aille à le déballer ?
-
-21
-00:10:49.647 --> 00:10:51.727
-Non, merci. Plus tard.
-
-22
-00:10:59.882 --> 00:11:10.782
-Si y a quelque chose qui m'a échappé une fois, c'est ma chatte en pêche de foie.
-
-22
-00:11:10.782 --> 00:11:20.402
-Oui, monsieur Jacques.
-
-22
-00:11:20.402 --> 00:11:22.022
-Il a été muté.
-
-22
-00:11:22.022 --> 00:11:24.882
-Il a fait une offre qu'il pouvait pas refuser.
-
-22
-00:11:24.882 --> 00:11:26.882
-Promotion ou quoi ?
-
-22
-00:11:26.882 --> 00:11:28.762
-Moi, j'avais compris.
-
-23
-00:11:29.876 --> 00:11:30.976
-Il m'en avait informé.
-
-23
-00:11:30.976 --> 00:11:35.176
-Donc c'est moi qui serai votre nouveau référent au Droneur.
-
-23
-00:11:35.176 --> 00:11:36.876
-Je m'appelle Raymond Cisteron.
-
-23
-00:11:36.876 --> 00:11:40.576
-Cisteron ?
-
-23
-00:11:40.576 --> 00:11:40.936
-Bon.
-
-23
-00:11:40.936 --> 00:11:42.996
-Et vous êtes un...
-
-23
-00:11:42.996 --> 00:11:44.476
-Oui, j'ai étudié les dossiers dans le détail.
-
-23
-00:11:44.476 --> 00:11:48.896
-Si vous êtes d'accord, on va commencer par celui du colonel Bazir.
-
-23
-00:11:48.896 --> 00:11:50.536
-D'accord.
-
-24
-00:11:59.871 --> 00:12:17.171
-Qu'est-ce que c'est ?
-
-24
-00:12:17.171 --> 00:12:19.131
-Château Giravates 2005.
-
-24
-00:12:19.131 --> 00:12:20.991
-Un Pauillac du très bon.
-
-24
-00:12:20.991 --> 00:12:24.251
-Vous voulez que je le boive ?
-
-24
-00:12:24.251 --> 00:12:25.711
-Vous voulez boire autre chose ?
-
-24
-00:12:25.711 --> 00:12:26.871
-La bière, la vodka ?
-
-25
-00:12:29.866 --> 00:12:34.026
-Je suis un peu embêté, mais vous savez que je n'ai pas le droit de boire de l'alcool, c'est contraire à ma religion.
-
-25
-00:12:34.026 --> 00:12:36.486
-Vous allez faire une entorse.
-
-25
-00:12:36.486 --> 00:12:41.286
-On doit tester votre capacité à réciter votre légende sous alcool.
-
-25
-00:12:41.286 --> 00:12:45.326
-Désolé, ce n'est pas possible.
-
-25
-00:12:45.326 --> 00:12:49.286
-Rachid, vous allez boire, ce n'est pas la peine de discuter, c'est le protocole.
-
-25
-00:12:49.286 --> 00:12:54.466
-En même temps, si vous m'envoyez à Alger, c'est aussi parce que je suis musulman.
-
-25
-00:12:55.286 --> 00:12:59.826
-Il est impossible de vous laisser partir si on ne sait pas comment vous tenez votre légende quand vous perdez le compte.
-
-26
-00:12:59.860 --> 00:13:00.260
-Je contrôle.
-
-26
-00:13:00.260 --> 00:13:06.900
-Si je n'avais jamais bu une goutte d'alcool,
-
-26
-00:13:06.900 --> 00:13:07.960
-je n'en boirais jamais de mon plein gré.
-
-26
-00:13:07.960 --> 00:13:11.160
-Si je me trouve dans une situation où on m'oblige à boire,
-
-26
-00:13:11.160 --> 00:13:12.780
-c'est que je suis déjà dans une très mauvaise posture.
-
-26
-00:13:12.780 --> 00:13:14.200
-Vous êtes d'accord ?
-
-26
-00:13:14.200 --> 00:13:15.480
-C'est que ma légende, elle pèse plus grand-chose.
-
-26
-00:13:15.480 --> 00:13:19.660
-Si on en est à me faire boire en Algérie,
-
-26
-00:13:19.660 --> 00:13:21.700
-c'est que c'est déjà trop tard.
-
-27
-00:13:29.855 --> 00:13:31.495
-Vous êtes vraiment buté.
-