diff --git a/.gitea/workflows/e2e.yml b/.gitea/workflows/e2e.yml index 7175982..268210e 100644 --- a/.gitea/workflows/e2e.yml +++ b/.gitea/workflows/e2e.yml @@ -13,6 +13,7 @@ on: - 'crates/arr-indexer/**' - 'crates/arr-meta/**' - 'crates/arr-probe/**' + - 'crates/arr-subs/**' - 'crates/arr-api/**' - 'crates/arr-daemon/**' - '.gitea/workflows/e2e.yml' @@ -70,10 +71,21 @@ jobs: curl -LsSf https://get.nexte.st/latest/linux \ | tar zxf - -C "$HOME/.cargo/bin" + - name: alass + # No distro package; the subtitle sync path (DESIGN.md §15) shells + # out to a binary literally named `alass`, which the crate is not. + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v alass >/dev/null || { + cargo install alass-cli --locked + ln -sf "$HOME/.cargo/bin/alass-cli" "$HOME/.cargo/bin/alass" + } + - name: build daemon # The harness spawns this binary; building it here keeps the compile - # out of the first test's boot window. - run: cargo build -p arr-daemon --bin arr + # out of the first test's boot window. `translate-command` is the + # subtitle e2e scenarios' stub-script translation backend. + run: cargo build -p arr-daemon --bin arr --features translate-command - name: e2e env: diff --git a/.sqlx/query-3be9c350ef24a69490540379225d95d2949fe2a0f1f59d55d6272cca1bd431aa.json b/.sqlx/query-028ba37575a1081f7678fd8649227f39c29afc1883e3a3607260455d1a088143.json similarity index 80% rename from .sqlx/query-3be9c350ef24a69490540379225d95d2949fe2a0f1f59d55d6272cca1bd431aa.json rename to .sqlx/query-028ba37575a1081f7678fd8649227f39c29afc1883e3a3607260455d1a088143.json index d962efc..e5a7b56 100644 --- a/.sqlx/query-3be9c350ef24a69490540379225d95d2949fe2a0f1f59d55d6272cca1bd431aa.json +++ b/.sqlx/query-028ba37575a1081f7678fd8649227f39c29afc1883e3a3607260455d1a088143.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id AS \"id!: i64\", path AS \"path!: String\"\n FROM media_files WHERE owner_kind = 'movie' AND owner_id = ?", + "query": "SELECT id AS \"id!: i64\", path AS \"path!: String\"\n FROM media_files WHERE owner_kind = 'movie' AND owner_id = ?", "describe": { "columns": [ { @@ -34,5 +34,5 @@ false ] }, - "hash": "3be9c350ef24a69490540379225d95d2949fe2a0f1f59d55d6272cca1bd431aa" + "hash": "028ba37575a1081f7678fd8649227f39c29afc1883e3a3607260455d1a088143" } diff --git a/.sqlx/query-05b5757f0fb7e6ab1176387854a14fc8db793871193b6f16e3719d73040e8ce8.json b/.sqlx/query-05b5757f0fb7e6ab1176387854a14fc8db793871193b6f16e3719d73040e8ce8.json new file mode 100644 index 0000000..78909c2 --- /dev/null +++ b/.sqlx/query-05b5757f0fb7e6ab1176387854a14fc8db793871193b6f16e3719d73040e8ce8.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "SELECT r.name AS \"name!: String\",\n r.parsed AS \"parsed!: serde_json::Value\"\n FROM episodes e\n JOIN grabs g ON g.target_kind = 'season' AND g.target_id = e.season_id\n JOIN releases r ON r.id = g.release_id\n WHERE e.id = ?\n ORDER BY g.imported_at DESC, g.id DESC LIMIT 1", + "describe": { + "columns": [ + { + "name": "name!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } + }, + { + "name": "parsed!: serde_json::Value", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "parsed" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false + ] + }, + "hash": "05b5757f0fb7e6ab1176387854a14fc8db793871193b6f16e3719d73040e8ce8" +} diff --git a/.sqlx/query-08c356bfcd7f7cd7a4bac0bf0ce44ce3276577cb18f32b0c01839150e7827bca.json b/.sqlx/query-08c356bfcd7f7cd7a4bac0bf0ce44ce3276577cb18f32b0c01839150e7827bca.json new file mode 100644 index 0000000..ab6b8a1 --- /dev/null +++ b/.sqlx/query-08c356bfcd7f7cd7a4bac0bf0ce44ce3276577cb18f32b0c01839150e7827bca.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO subtitle_attempts\n (media_file_id, language, state, attempts, last_attempt_at, last_failure)\n VALUES (?, ?, ?, 1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), ?)\n ON CONFLICT (media_file_id, language) DO UPDATE\n SET state = excluded.state,\n attempts = subtitle_attempts.attempts + 1,\n last_attempt_at = excluded.last_attempt_at,\n last_failure = excluded.last_failure,\n updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "08c356bfcd7f7cd7a4bac0bf0ce44ce3276577cb18f32b0c01839150e7827bca" +} diff --git a/.sqlx/query-0b40dbc80628244531a044e872238e356ab6bc66567c72008cff506e595932b1.json b/.sqlx/query-0b40dbc80628244531a044e872238e356ab6bc66567c72008cff506e595932b1.json new file mode 100644 index 0000000..fb01335 --- /dev/null +++ b/.sqlx/query-0b40dbc80628244531a044e872238e356ab6bc66567c72008cff506e595932b1.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT EXISTS(SELECT 1 FROM episodes WHERE id = ?) AS \"exists!: bool\"", + "describe": { + "columns": [ + { + "name": "exists!: bool", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "0b40dbc80628244531a044e872238e356ab6bc66567c72008cff506e595932b1" +} diff --git a/.sqlx/query-0f94b0f839cfcbe4a70ee5be81f452c88dc610d05042d4af811963c0cbc6113e.json b/.sqlx/query-0f94b0f839cfcbe4a70ee5be81f452c88dc610d05042d4af811963c0cbc6113e.json new file mode 100644 index 0000000..fb99da4 --- /dev/null +++ b/.sqlx/query-0f94b0f839cfcbe4a70ee5be81f452c88dc610d05042d4af811963c0cbc6113e.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT wanted_languages FROM subtitle_settings WHERE id = 1", + "describe": { + "columns": [ + { + "name": "wanted_languages", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "wanted_languages" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "0f94b0f839cfcbe4a70ee5be81f452c88dc610d05042d4af811963c0cbc6113e" +} diff --git a/.sqlx/query-2084928704dbb8a2c81692b9aa3b6edff64745945ea4e88d70bcb139e272c857.json b/.sqlx/query-2084928704dbb8a2c81692b9aa3b6edff64745945ea4e88d70bcb139e272c857.json new file mode 100644 index 0000000..5eaad0c --- /dev/null +++ b/.sqlx/query-2084928704dbb8a2c81692b9aa3b6edff64745945ea4e88d70bcb139e272c857.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT EXISTS(SELECT 1 FROM series WHERE id = ?) AS \"exists!: bool\"", + "describe": { + "columns": [ + { + "name": "exists!: bool", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "2084928704dbb8a2c81692b9aa3b6edff64745945ea4e88d70bcb139e272c857" +} diff --git a/.sqlx/query-233bae03912e50e6bcde4b24a9d7fbe256dbf626e4e236a06ba08a00643566d3.json b/.sqlx/query-233bae03912e50e6bcde4b24a9d7fbe256dbf626e4e236a06ba08a00643566d3.json new file mode 100644 index 0000000..c333a8b --- /dev/null +++ b/.sqlx/query-233bae03912e50e6bcde4b24a9d7fbe256dbf626e4e236a06ba08a00643566d3.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT EXISTS(\n SELECT 1 FROM subtitle_files\n WHERE media_file_id = ? AND language = ? AND forced = 0\n ) AS \"exists!: bool\"", + "describe": { + "columns": [ + { + "name": "exists!: bool", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false + ] + }, + "hash": "233bae03912e50e6bcde4b24a9d7fbe256dbf626e4e236a06ba08a00643566d3" +} diff --git a/.sqlx/query-268cb11282b16494330c1655ba3dfcb4f2506686822711f3c6489c8ddd42b0e7.json b/.sqlx/query-268cb11282b16494330c1655ba3dfcb4f2506686822711f3c6489c8ddd42b0e7.json new file mode 100644 index 0000000..2eafbdc --- /dev/null +++ b/.sqlx/query-268cb11282b16494330c1655ba3dfcb4f2506686822711f3c6489c8ddd42b0e7.json @@ -0,0 +1,50 @@ +{ + "db_name": "SQLite", + "query": "SELECT sr.tmdb_id AS \"tmdb_id!: i64\",\n s.number AS \"season!: i64\",\n e.number AS \"episode!: i64\"\n FROM episodes e\n JOIN seasons s ON s.id = e.season_id\n JOIN series sr ON sr.id = s.series_id\n WHERE e.id = ?", + "describe": { + "columns": [ + { + "name": "tmdb_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } + }, + { + "name": "season!: i64", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } + }, + { + "name": "episode!: i64", + "ordinal": 2, + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "268cb11282b16494330c1655ba3dfcb4f2506686822711f3c6489c8ddd42b0e7" +} diff --git a/.sqlx/query-295476f5ee76f552a0d0727fca01f7602111b1dc5614bf2f8c8626744ecb22db.json b/.sqlx/query-295476f5ee76f552a0d0727fca01f7602111b1dc5614bf2f8c8626744ecb22db.json new file mode 100644 index 0000000..da5a15a --- /dev/null +++ b/.sqlx/query-295476f5ee76f552a0d0727fca01f7602111b1dc5614bf2f8c8626744ecb22db.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "SELECT sf.id AS \"id!: i64\", sf.path AS \"path!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id\n WHERE mf.owner_kind = 'movie' AND mf.owner_id = ? AND sf.path IS NOT NULL", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "id" + } + } + }, + { + "name": "path!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true + ] + }, + "hash": "295476f5ee76f552a0d0727fca01f7602111b1dc5614bf2f8c8626744ecb22db" +} diff --git a/.sqlx/query-2afeefde38499d722f329d1d472bc89524ce6498ccd4098ba97979e0621b840b.json b/.sqlx/query-2afeefde38499d722f329d1d472bc89524ce6498ccd4098ba97979e0621b840b.json new file mode 100644 index 0000000..d62efc1 --- /dev/null +++ b/.sqlx/query-2afeefde38499d722f329d1d472bc89524ce6498ccd4098ba97979e0621b840b.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT wanted_languages AS \"wanted_languages!: String\" FROM subtitle_settings WHERE id = 1", + "describe": { + "columns": [ + { + "name": "wanted_languages!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "wanted_languages" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "2afeefde38499d722f329d1d472bc89524ce6498ccd4098ba97979e0621b840b" +} diff --git a/.sqlx/query-2b67abe4db9e00517fecfa53fbad5bbbc633a9f537eaaaa9d46a445d8c25eb2c.json b/.sqlx/query-2b67abe4db9e00517fecfa53fbad5bbbc633a9f537eaaaa9d46a445d8c25eb2c.json new file mode 100644 index 0000000..afdbb22 --- /dev/null +++ b/.sqlx/query-2b67abe4db9e00517fecfa53fbad5bbbc633a9f537eaaaa9d46a445d8c25eb2c.json @@ -0,0 +1,122 @@ +{ + "db_name": "SQLite", + "query": "SELECT m.id AS \"movie_id!: i64\", m.tmdb_id AS \"tmdb_id!: i64\",\n m.title AS \"title!: String\", m.year, m.poster_path,\n mf.id AS \"media_file_id!: i64\", sa.language AS \"language!: String\",\n sa.state AS \"state!: arr_db::SubtitleState\", sa.last_failure\n FROM subtitle_attempts sa\n JOIN media_files mf ON mf.id = sa.media_file_id AND mf.owner_kind = 'movie'\n JOIN movies m ON m.id = mf.owner_id\n WHERE sa.state IN ('failed', 'capped', 'unavailable')\n ORDER BY m.title, sa.language", + "describe": { + "columns": [ + { + "name": "movie_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } + }, + { + "name": "tmdb_id!: i64", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } + }, + { + "name": "title!: String", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } + }, + { + "name": "year", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } + }, + { + "name": "poster_path", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } + }, + { + "name": "media_file_id!: i64", + "ordinal": 5, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "id" + } + } + }, + { + "name": "language!: String", + "ordinal": 6, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "language" + } + } + }, + { + "name": "state!: arr_db::SubtitleState", + "ordinal": 7, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "state" + } + } + }, + { + "name": "last_failure", + "ordinal": 8, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "last_failure" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false, + true, + true, + false, + false, + false, + true + ] + }, + "hash": "2b67abe4db9e00517fecfa53fbad5bbbc633a9f537eaaaa9d46a445d8c25eb2c" +} diff --git a/.sqlx/query-31bed4c9097d6ea2ed189de48440bd0d882873d27450e5d177baed65c74fd7de.json b/.sqlx/query-31bed4c9097d6ea2ed189de48440bd0d882873d27450e5d177baed65c74fd7de.json new file mode 100644 index 0000000..aa62b4c --- /dev/null +++ b/.sqlx/query-31bed4c9097d6ea2ed189de48440bd0d882873d27450e5d177baed65c74fd7de.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE subtitle_attempts\n SET state = 'wanted',\n updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')\n WHERE media_file_id = ? AND language = ? AND state = 'satisfied'", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "31bed4c9097d6ea2ed189de48440bd0d882873d27450e5d177baed65c74fd7de" +} diff --git a/.sqlx/query-32443dc6e5bdd414340443c5d527b2578bcdeebd053b667db35423d829d4bf10.json b/.sqlx/query-32443dc6e5bdd414340443c5d527b2578bcdeebd053b667db35423d829d4bf10.json new file mode 100644 index 0000000..d4468bd --- /dev/null +++ b/.sqlx/query-32443dc6e5bdd414340443c5d527b2578bcdeebd053b667db35423d829d4bf10.json @@ -0,0 +1,74 @@ +{ + "db_name": "SQLite", + "query": "SELECT id AS \"id!: i64\",\n path AS \"path!: String\",\n size AS \"size!: i64\",\n owner_kind AS \"owner_kind!: String\",\n owner_id AS \"owner_id!: i64\"\n FROM media_files WHERE id = ?", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "id" + } + } + }, + { + "name": "path!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "path" + } + } + }, + { + "name": "size!: i64", + "ordinal": 2, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "size" + } + } + }, + { + "name": "owner_kind!: String", + "ordinal": 3, + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "owner_kind" + } + } + }, + { + "name": "owner_id!: i64", + "ordinal": 4, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "owner_id" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "32443dc6e5bdd414340443c5d527b2578bcdeebd053b667db35423d829d4bf10" +} diff --git a/.sqlx/query-35ace0417f613340f82522549d70d0dffeb680afb57511cb17fa15b3a571b974.json b/.sqlx/query-35ace0417f613340f82522549d70d0dffeb680afb57511cb17fa15b3a571b974.json new file mode 100644 index 0000000..25fe714 --- /dev/null +++ b/.sqlx/query-35ace0417f613340f82522549d70d0dffeb680afb57511cb17fa15b3a571b974.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO subtitle_attempts (media_file_id, language)\n SELECT id, ? FROM media_files WHERE probed IS NOT NULL\n ON CONFLICT (media_file_id, language) DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "35ace0417f613340f82522549d70d0dffeb680afb57511cb17fa15b3a571b974" +} diff --git a/.sqlx/query-3d48b78768bf8dc2e337d0935889a5654089f38bb7ee2a15b7e19f4ed5540262.json b/.sqlx/query-3d48b78768bf8dc2e337d0935889a5654089f38bb7ee2a15b7e19f4ed5540262.json new file mode 100644 index 0000000..b32d119 --- /dev/null +++ b/.sqlx/query-3d48b78768bf8dc2e337d0935889a5654089f38bb7ee2a15b7e19f4ed5540262.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO subtitle_files\n (media_file_id, language, origin, provider, candidate_id, engine,\n forced, sdh, synced, sync_rejected, path)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (path) DO NOTHING\n ON CONFLICT (media_file_id, language, forced, sdh)\n WHERE origin = 'embedded' DO NOTHING\n RETURNING id AS \"id!: i64\"", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "id" + } + } + } + ], + "parameters": { + "Right": 11 + }, + "nullable": [ + null + ] + }, + "hash": "3d48b78768bf8dc2e337d0935889a5654089f38bb7ee2a15b7e19f4ed5540262" +} diff --git a/.sqlx/query-432cf38775bc4d7dee1dd9d009e06638ea57f805127f7634f34b8189e831f461.json b/.sqlx/query-432cf38775bc4d7dee1dd9d009e06638ea57f805127f7634f34b8189e831f461.json new file mode 100644 index 0000000..40ce09c --- /dev/null +++ b/.sqlx/query-432cf38775bc4d7dee1dd9d009e06638ea57f805127f7634f34b8189e831f461.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "SELECT r.name AS \"name!: String\",\n r.parsed AS \"parsed!: serde_json::Value\"\n FROM grabs g JOIN releases r ON r.id = g.release_id\n WHERE g.target_kind = ? AND g.target_id = ?\n ORDER BY g.imported_at DESC, g.id DESC LIMIT 1", + "describe": { + "columns": [ + { + "name": "name!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } + }, + { + "name": "parsed!: serde_json::Value", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "parsed" + } + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false + ] + }, + "hash": "432cf38775bc4d7dee1dd9d009e06638ea57f805127f7634f34b8189e831f461" +} diff --git a/.sqlx/query-47ad9162ba373dfbdf061898f5f148e65de261b494453804db912a919a78c2c5.json b/.sqlx/query-47ad9162ba373dfbdf061898f5f148e65de261b494453804db912a919a78c2c5.json new file mode 100644 index 0000000..43e6566 --- /dev/null +++ b/.sqlx/query-47ad9162ba373dfbdf061898f5f148e65de261b494453804db912a919a78c2c5.json @@ -0,0 +1,98 @@ +{ + "db_name": "SQLite", + "query": "SELECT a.media_file_id AS \"media_file_id!: i64\",\n a.language AS \"language!: String\",\n a.state AS \"state!: SubtitleState\",\n a.attempts AS \"attempts!: i64\",\n a.last_attempt_at,\n a.last_failure,\n f.path AS \"media_file_path!: String\"\n FROM subtitle_attempts a\n JOIN media_files f ON f.id = a.media_file_id\n WHERE a.state IN ('wanted', 'failed')\n ORDER BY f.created_at DESC, a.last_attempt_at, a.language\n LIMIT ?", + "describe": { + "columns": [ + { + "name": "media_file_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "media_file_id" + } + } + }, + { + "name": "language!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "language" + } + } + }, + { + "name": "state!: SubtitleState", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "state" + } + } + }, + { + "name": "attempts!: i64", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "attempts" + } + } + }, + { + "name": "last_attempt_at", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "last_attempt_at" + } + } + }, + { + "name": "last_failure", + "ordinal": 5, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "last_failure" + } + } + }, + { + "name": "media_file_path!: String", + "ordinal": 6, + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + true, + false + ] + }, + "hash": "47ad9162ba373dfbdf061898f5f148e65de261b494453804db912a919a78c2c5" +} diff --git a/.sqlx/query-4f4f037c956ac6b246503354ff9b59db59e36f9a6d59adc0753b95662c9c630a.json b/.sqlx/query-4f4f037c956ac6b246503354ff9b59db59e36f9a6d59adc0753b95662c9c630a.json new file mode 100644 index 0000000..3351356 --- /dev/null +++ b/.sqlx/query-4f4f037c956ac6b246503354ff9b59db59e36f9a6d59adc0753b95662c9c630a.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT tmdb_id AS \"tmdb_id!: i64\" FROM movies WHERE id = ?", + "describe": { + "columns": [ + { + "name": "tmdb_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "4f4f037c956ac6b246503354ff9b59db59e36f9a6d59adc0753b95662c9c630a" +} diff --git a/.sqlx/query-5153426ad8b36b6a2b8c17942dfedf02b1e1f91482c8784357d2ef61c9d160c1.json b/.sqlx/query-5153426ad8b36b6a2b8c17942dfedf02b1e1f91482c8784357d2ef61c9d160c1.json new file mode 100644 index 0000000..284077b --- /dev/null +++ b/.sqlx/query-5153426ad8b36b6a2b8c17942dfedf02b1e1f91482c8784357d2ef61c9d160c1.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "SELECT sf.id AS \"id!: i64\", sf.path AS \"path!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id\n JOIN episodes e ON mf.owner_kind = 'episode' AND e.id = mf.owner_id\n JOIN seasons se ON se.id = e.season_id\n WHERE se.series_id = ? AND sf.path IS NOT NULL", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "id" + } + } + }, + { + "name": "path!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true + ] + }, + "hash": "5153426ad8b36b6a2b8c17942dfedf02b1e1f91482c8784357d2ef61c9d160c1" +} diff --git a/.sqlx/query-5f3a663eef0318b38cd864a2e06befa20fba22fc2d281295aed81a851a35ea07.json b/.sqlx/query-5f3a663eef0318b38cd864a2e06befa20fba22fc2d281295aed81a851a35ea07.json new file mode 100644 index 0000000..e7c8adc --- /dev/null +++ b/.sqlx/query-5f3a663eef0318b38cd864a2e06befa20fba22fc2d281295aed81a851a35ea07.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT sf.path AS \"path!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id\n JOIN episodes e ON mf.owner_kind = 'episode' AND e.id = mf.owner_id\n JOIN seasons se ON se.id = e.season_id\n WHERE se.series_id = ? AND sf.path IS NOT NULL", + "describe": { + "columns": [ + { + "name": "path!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true + ] + }, + "hash": "5f3a663eef0318b38cd864a2e06befa20fba22fc2d281295aed81a851a35ea07" +} diff --git a/.sqlx/query-6236e1be0e268c678271dd72be52454347da6637f0bf3ecb52dda82cddfe459a.json b/.sqlx/query-6236e1be0e268c678271dd72be52454347da6637f0bf3ecb52dda82cddfe459a.json new file mode 100644 index 0000000..a87ef8d --- /dev/null +++ b/.sqlx/query-6236e1be0e268c678271dd72be52454347da6637f0bf3ecb52dda82cddfe459a.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO subtitle_attempts (media_file_id, language)\n VALUES (?, ?)\n ON CONFLICT (media_file_id, language) DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "6236e1be0e268c678271dd72be52454347da6637f0bf3ecb52dda82cddfe459a" +} diff --git a/.sqlx/query-62d625c322c8c64b48317cdbf466d74043708d8e12997e0efec23a18eaaaaace.json b/.sqlx/query-62d625c322c8c64b48317cdbf466d74043708d8e12997e0efec23a18eaaaaace.json new file mode 100644 index 0000000..a1bef68 --- /dev/null +++ b/.sqlx/query-62d625c322c8c64b48317cdbf466d74043708d8e12997e0efec23a18eaaaaace.json @@ -0,0 +1,158 @@ +{ + "db_name": "SQLite", + "query": "SELECT id AS \"id!: i64\",\n media_file_id AS \"media_file_id!: i64\",\n language AS \"language!: String\",\n origin AS \"origin!: SubtitleOrigin\",\n provider,\n candidate_id,\n engine,\n forced AS \"forced!: bool\",\n sdh AS \"sdh!: bool\",\n synced AS \"synced!: bool\",\n sync_rejected AS \"sync_rejected!: bool\",\n path\n FROM subtitle_files\n WHERE media_file_id = ?\n ORDER BY language, id", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "id" + } + } + }, + { + "name": "media_file_id!: i64", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "media_file_id" + } + } + }, + { + "name": "language!: String", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "language" + } + } + }, + { + "name": "origin!: SubtitleOrigin", + "ordinal": 3, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "origin" + } + } + }, + { + "name": "provider", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "provider" + } + } + }, + { + "name": "candidate_id", + "ordinal": 5, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "candidate_id" + } + } + }, + { + "name": "engine", + "ordinal": 6, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "engine" + } + } + }, + { + "name": "forced!: bool", + "ordinal": 7, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "forced" + } + } + }, + { + "name": "sdh!: bool", + "ordinal": 8, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "sdh" + } + } + }, + { + "name": "synced!: bool", + "ordinal": 9, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "synced" + } + } + }, + { + "name": "sync_rejected!: bool", + "ordinal": 10, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "sync_rejected" + } + } + }, + { + "name": "path", + "ordinal": 11, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + true + ] + }, + "hash": "62d625c322c8c64b48317cdbf466d74043708d8e12997e0efec23a18eaaaaace" +} diff --git a/.sqlx/query-69f34468a61979b85b7513056d58e34c743af3ae62d5327d4dea49fe7586a85b.json b/.sqlx/query-69f34468a61979b85b7513056d58e34c743af3ae62d5327d4dea49fe7586a85b.json new file mode 100644 index 0000000..1d494d9 --- /dev/null +++ b/.sqlx/query-69f34468a61979b85b7513056d58e34c743af3ae62d5327d4dea49fe7586a85b.json @@ -0,0 +1,98 @@ +{ + "db_name": "SQLite", + "query": "SELECT m.id AS \"movie_id!: i64\", m.tmdb_id AS \"tmdb_id!: i64\",\n m.title AS \"title!: String\", m.year, m.poster_path,\n mf.id AS \"media_file_id!: i64\", sf.language AS \"language!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id AND mf.owner_kind = 'movie'\n JOIN movies m ON m.id = mf.owner_id\n WHERE sf.sync_rejected = 1\n ORDER BY m.title, sf.language", + "describe": { + "columns": [ + { + "name": "movie_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } + }, + { + "name": "tmdb_id!: i64", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } + }, + { + "name": "title!: String", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } + }, + { + "name": "year", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } + }, + { + "name": "poster_path", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } + }, + { + "name": "media_file_id!: i64", + "ordinal": 5, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "id" + } + } + }, + { + "name": "language!: String", + "ordinal": 6, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "language" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false, + true, + true, + false, + false + ] + }, + "hash": "69f34468a61979b85b7513056d58e34c743af3ae62d5327d4dea49fe7586a85b" +} diff --git a/.sqlx/query-6cdb9a9344bbab557433da35471968b6e5cf96b5b220ffb41f10b6d851884803.json b/.sqlx/query-6cdb9a9344bbab557433da35471968b6e5cf96b5b220ffb41f10b6d851884803.json new file mode 100644 index 0000000..316cb2a --- /dev/null +++ b/.sqlx/query-6cdb9a9344bbab557433da35471968b6e5cf96b5b220ffb41f10b6d851884803.json @@ -0,0 +1,158 @@ +{ + "db_name": "SQLite", + "query": "SELECT s.id AS \"series_id!: i64\", s.tmdb_id AS \"tmdb_id!: i64\",\n s.title AS \"title!: String\", s.year, s.poster_path,\n e.id AS \"episode_id!: i64\", se.number AS \"season_number!: i64\",\n e.number AS \"episode_number!: i64\", mf.id AS \"media_file_id!: i64\",\n sa.language AS \"language!: String\",\n sa.state AS \"state!: arr_db::SubtitleState\", sa.last_failure\n FROM subtitle_attempts sa\n JOIN media_files mf ON mf.id = sa.media_file_id AND mf.owner_kind = 'episode'\n JOIN episodes e ON e.id = mf.owner_id\n JOIN seasons se ON se.id = e.season_id\n JOIN series s ON s.id = se.series_id\n WHERE sa.state IN ('failed', 'capped', 'unavailable')\n ORDER BY s.title, se.number, e.number, sa.language", + "describe": { + "columns": [ + { + "name": "series_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } + }, + { + "name": "tmdb_id!: i64", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } + }, + { + "name": "title!: String", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } + }, + { + "name": "year", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } + }, + { + "name": "poster_path", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } + }, + { + "name": "episode_id!: i64", + "ordinal": 5, + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } + }, + { + "name": "season_number!: i64", + "ordinal": 6, + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } + }, + { + "name": "episode_number!: i64", + "ordinal": 7, + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } + }, + { + "name": "media_file_id!: i64", + "ordinal": 8, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "id" + } + } + }, + { + "name": "language!: String", + "ordinal": 9, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "language" + } + } + }, + { + "name": "state!: arr_db::SubtitleState", + "ordinal": 10, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "state" + } + } + }, + { + "name": "last_failure", + "ordinal": 11, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "last_failure" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false, + true, + true, + false, + false, + false, + false, + false, + false, + true + ] + }, + "hash": "6cdb9a9344bbab557433da35471968b6e5cf96b5b220ffb41f10b6d851884803" +} diff --git a/.sqlx/query-6e27f3a4c642ac324ef5ae2017a54b93f1d95aa54c46ff7e169278a295d1a2cc.json b/.sqlx/query-6e27f3a4c642ac324ef5ae2017a54b93f1d95aa54c46ff7e169278a295d1a2cc.json new file mode 100644 index 0000000..b8ae164 --- /dev/null +++ b/.sqlx/query-6e27f3a4c642ac324ef5ae2017a54b93f1d95aa54c46ff7e169278a295d1a2cc.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO subtitle_budget_spend (kind, name, day, spent)\n SELECT ?1, ?2, strftime('%Y-%m-%d', 'now'), ?3\n WHERE ?3 <= ?4\n ON CONFLICT (kind, name, day) DO UPDATE\n SET spent = subtitle_budget_spend.spent + excluded.spent\n WHERE subtitle_budget_spend.spent + excluded.spent <= ?4\n RETURNING spent AS \"spent!: i64\"", + "describe": { + "columns": [ + { + "name": "spent!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_budget_spend", + "name": "spent" + } + } + } + ], + "parameters": { + "Right": 4 + }, + "nullable": [ + false + ] + }, + "hash": "6e27f3a4c642ac324ef5ae2017a54b93f1d95aa54c46ff7e169278a295d1a2cc" +} diff --git a/.sqlx/query-74178ac3ec41aa8a5ce164103a8d8301cb555eea2fa5d655a7365fd02c557cce.json b/.sqlx/query-74178ac3ec41aa8a5ce164103a8d8301cb555eea2fa5d655a7365fd02c557cce.json new file mode 100644 index 0000000..1cedab9 --- /dev/null +++ b/.sqlx/query-74178ac3ec41aa8a5ce164103a8d8301cb555eea2fa5d655a7365fd02c557cce.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM subtitle_attempts WHERE language = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "74178ac3ec41aa8a5ce164103a8d8301cb555eea2fa5d655a7365fd02c557cce" +} diff --git a/.sqlx/query-77e55f2aa534468cf6e599d0c01679653553a68f2f1b9b5516f5d2fb42974b05.json b/.sqlx/query-77e55f2aa534468cf6e599d0c01679653553a68f2f1b9b5516f5d2fb42974b05.json new file mode 100644 index 0000000..28090cc --- /dev/null +++ b/.sqlx/query-77e55f2aa534468cf6e599d0c01679653553a68f2f1b9b5516f5d2fb42974b05.json @@ -0,0 +1,74 @@ +{ + "db_name": "SQLite", + "query": "SELECT wanted_languages AS \"wanted_languages!: String\",\n providers_enabled AS \"providers_enabled!: String\",\n translation_engine AS \"translation_engine: String\",\n provider_daily_budgets AS \"provider_daily_budgets!: String\",\n translator_daily_budgets AS \"translator_daily_budgets!: String\"\n FROM subtitle_settings WHERE id = 1", + "describe": { + "columns": [ + { + "name": "wanted_languages!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "wanted_languages" + } + } + }, + { + "name": "providers_enabled!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "providers_enabled" + } + } + }, + { + "name": "translation_engine: String", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "translation_engine" + } + } + }, + { + "name": "provider_daily_budgets!: String", + "ordinal": 3, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "provider_daily_budgets" + } + } + }, + { + "name": "translator_daily_budgets!: String", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "translator_daily_budgets" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + true, + false, + false + ] + }, + "hash": "77e55f2aa534468cf6e599d0c01679653553a68f2f1b9b5516f5d2fb42974b05" +} diff --git a/.sqlx/query-7c501e25106ea8d4ad9f28ca904c446551c6afd68d85e2231742bebd215a1a4b.json b/.sqlx/query-7c501e25106ea8d4ad9f28ca904c446551c6afd68d85e2231742bebd215a1a4b.json new file mode 100644 index 0000000..7d9b866 --- /dev/null +++ b/.sqlx/query-7c501e25106ea8d4ad9f28ca904c446551c6afd68d85e2231742bebd215a1a4b.json @@ -0,0 +1,50 @@ +{ + "db_name": "SQLite", + "query": "SELECT remote_command_timeout_seconds AS \"remote_command_timeout_seconds!: i64\",\n openai_base_url AS \"openai_base_url: String\",\n openai_model AS \"openai_model: String\"\n FROM subtitle_settings WHERE id = 1", + "describe": { + "columns": [ + { + "name": "remote_command_timeout_seconds!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "remote_command_timeout_seconds" + } + } + }, + { + "name": "openai_base_url: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "openai_base_url" + } + } + }, + { + "name": "openai_model: String", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "openai_model" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + true, + true + ] + }, + "hash": "7c501e25106ea8d4ad9f28ca904c446551c6afd68d85e2231742bebd215a1a4b" +} diff --git a/.sqlx/query-82bb36910db14d5471117285ef2d3a9f8efefc5d400a8f94e1f4aed321284504.json b/.sqlx/query-82bb36910db14d5471117285ef2d3a9f8efefc5d400a8f94e1f4aed321284504.json new file mode 100644 index 0000000..4df707c --- /dev/null +++ b/.sqlx/query-82bb36910db14d5471117285ef2d3a9f8efefc5d400a8f94e1f4aed321284504.json @@ -0,0 +1,110 @@ +{ + "db_name": "SQLite", + "query": "SELECT wanted_languages AS \"wanted_languages!: String\",\n providers_enabled AS \"providers_enabled!: String\",\n translation_engine AS \"translation_engine: String\",\n provider_daily_budgets AS \"provider_daily_budgets!: String\",\n translator_daily_budgets AS \"translator_daily_budgets!: String\",\n remote_command_timeout_seconds AS \"remote_command_timeout_seconds!: i64\",\n openai_base_url AS \"openai_base_url: String\",\n openai_model AS \"openai_model: String\"\n FROM subtitle_settings WHERE id = 1", + "describe": { + "columns": [ + { + "name": "wanted_languages!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "wanted_languages" + } + } + }, + { + "name": "providers_enabled!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "providers_enabled" + } + } + }, + { + "name": "translation_engine: String", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "translation_engine" + } + } + }, + { + "name": "provider_daily_budgets!: String", + "ordinal": 3, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "provider_daily_budgets" + } + } + }, + { + "name": "translator_daily_budgets!: String", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "translator_daily_budgets" + } + } + }, + { + "name": "remote_command_timeout_seconds!: i64", + "ordinal": 5, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "remote_command_timeout_seconds" + } + } + }, + { + "name": "openai_base_url: String", + "ordinal": 6, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "openai_base_url" + } + } + }, + { + "name": "openai_model: String", + "ordinal": 7, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "openai_model" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + true, + true + ] + }, + "hash": "82bb36910db14d5471117285ef2d3a9f8efefc5d400a8f94e1f4aed321284504" +} diff --git a/.sqlx/query-8543e4ad8c59e83911688ceace72f66d360240a45eee05aaf15dab233e22e7e6.json b/.sqlx/query-8543e4ad8c59e83911688ceace72f66d360240a45eee05aaf15dab233e22e7e6.json new file mode 100644 index 0000000..3acf94b --- /dev/null +++ b/.sqlx/query-8543e4ad8c59e83911688ceace72f66d360240a45eee05aaf15dab233e22e7e6.json @@ -0,0 +1,134 @@ +{ + "db_name": "SQLite", + "query": "SELECT s.id AS \"series_id!: i64\", s.tmdb_id AS \"tmdb_id!: i64\",\n s.title AS \"title!: String\", s.year, s.poster_path,\n e.id AS \"episode_id!: i64\", se.number AS \"season_number!: i64\",\n e.number AS \"episode_number!: i64\", mf.id AS \"media_file_id!: i64\",\n sf.language AS \"language!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id AND mf.owner_kind = 'episode'\n JOIN episodes e ON e.id = mf.owner_id\n JOIN seasons se ON se.id = e.season_id\n JOIN series s ON s.id = se.series_id\n WHERE sf.sync_rejected = 1\n ORDER BY s.title, se.number, e.number, sf.language", + "describe": { + "columns": [ + { + "name": "series_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } + }, + { + "name": "tmdb_id!: i64", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } + }, + { + "name": "title!: String", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } + }, + { + "name": "year", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } + }, + { + "name": "poster_path", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } + }, + { + "name": "episode_id!: i64", + "ordinal": 5, + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } + }, + { + "name": "season_number!: i64", + "ordinal": 6, + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } + }, + { + "name": "episode_number!: i64", + "ordinal": 7, + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } + }, + { + "name": "media_file_id!: i64", + "ordinal": 8, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "id" + } + } + }, + { + "name": "language!: String", + "ordinal": 9, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "language" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false, + true, + true, + false, + false, + false, + false, + false + ] + }, + "hash": "8543e4ad8c59e83911688ceace72f66d360240a45eee05aaf15dab233e22e7e6" +} diff --git a/.sqlx/query-874079e2ed732468a1f249e6aa026753c78042e164d11eb7cc014c9500211b64.json b/.sqlx/query-874079e2ed732468a1f249e6aa026753c78042e164d11eb7cc014c9500211b64.json new file mode 100644 index 0000000..db9b8ae --- /dev/null +++ b/.sqlx/query-874079e2ed732468a1f249e6aa026753c78042e164d11eb7cc014c9500211b64.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE subtitle_files\n SET synced = ?, sync_rejected = ?,\n updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')\n WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "874079e2ed732468a1f249e6aa026753c78042e164d11eb7cc014c9500211b64" +} diff --git a/.sqlx/query-90480a6b6e724d7e4d05239799ad2f586c57deea100320fe433da007396f1e48.json b/.sqlx/query-90480a6b6e724d7e4d05239799ad2f586c57deea100320fe433da007396f1e48.json new file mode 100644 index 0000000..264f610 --- /dev/null +++ b/.sqlx/query-90480a6b6e724d7e4d05239799ad2f586c57deea100320fe433da007396f1e48.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "DELETE FROM subtitle_files WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "90480a6b6e724d7e4d05239799ad2f586c57deea100320fe433da007396f1e48" +} diff --git a/.sqlx/query-9091de460ba1cb47c9faa24fe37fee03615e7b23b10ceef88ee12c8deb0dbaaf.json b/.sqlx/query-9091de460ba1cb47c9faa24fe37fee03615e7b23b10ceef88ee12c8deb0dbaaf.json new file mode 100644 index 0000000..e6aab24 --- /dev/null +++ b/.sqlx/query-9091de460ba1cb47c9faa24fe37fee03615e7b23b10ceef88ee12c8deb0dbaaf.json @@ -0,0 +1,50 @@ +{ + "db_name": "SQLite", + "query": "SELECT media_file_id AS \"media_file_id!: i64\",\n language AS \"language!: String\",\n path\n FROM subtitle_files WHERE id = ?", + "describe": { + "columns": [ + { + "name": "media_file_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "media_file_id" + } + } + }, + { + "name": "language!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "language" + } + } + }, + { + "name": "path", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + true + ] + }, + "hash": "9091de460ba1cb47c9faa24fe37fee03615e7b23b10ceef88ee12c8deb0dbaaf" +} diff --git a/.sqlx/query-92a3d21df1ee60492a1e0742f1425c73a16ee7dd38bfdf2d7166db95d663dbf4.json b/.sqlx/query-92a3d21df1ee60492a1e0742f1425c73a16ee7dd38bfdf2d7166db95d663dbf4.json new file mode 100644 index 0000000..baa8cd2 --- /dev/null +++ b/.sqlx/query-92a3d21df1ee60492a1e0742f1425c73a16ee7dd38bfdf2d7166db95d663dbf4.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "SELECT sf.id AS \"id!: i64\", sf.path AS \"path!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id\n JOIN episodes e ON mf.owner_kind = 'episode' AND e.id = mf.owner_id\n JOIN seasons se ON se.id = e.season_id\n JOIN series s ON s.id = se.series_id\n WHERE s.root_id = ? AND sf.path IS NOT NULL\n ORDER BY sf.id", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "id" + } + } + }, + { + "name": "path!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true + ] + }, + "hash": "92a3d21df1ee60492a1e0742f1425c73a16ee7dd38bfdf2d7166db95d663dbf4" +} diff --git a/.sqlx/query-9b129ff8a5951c9dfcb9778da0174ac66d4307936323b1eff9440adb4c34b893.json b/.sqlx/query-9b129ff8a5951c9dfcb9778da0174ac66d4307936323b1eff9440adb4c34b893.json new file mode 100644 index 0000000..155eda7 --- /dev/null +++ b/.sqlx/query-9b129ff8a5951c9dfcb9778da0174ac66d4307936323b1eff9440adb4c34b893.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE subtitle_files SET path = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "9b129ff8a5951c9dfcb9778da0174ac66d4307936323b1eff9440adb4c34b893" +} diff --git a/.sqlx/query-9ee9ff74406e333df50076d1c456f0a9672173af252f36e5d75d2a9df99ab68d.json b/.sqlx/query-9ee9ff74406e333df50076d1c456f0a9672173af252f36e5d75d2a9df99ab68d.json new file mode 100644 index 0000000..254207c --- /dev/null +++ b/.sqlx/query-9ee9ff74406e333df50076d1c456f0a9672173af252f36e5d75d2a9df99ab68d.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT spent AS \"spent!: i64\" FROM subtitle_budget_spend\n WHERE kind = ? AND name = ? AND day = strftime('%Y-%m-%d', 'now')", + "describe": { + "columns": [ + { + "name": "spent!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_budget_spend", + "name": "spent" + } + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false + ] + }, + "hash": "9ee9ff74406e333df50076d1c456f0a9672173af252f36e5d75d2a9df99ab68d" +} diff --git a/.sqlx/query-a1256d6eec88a6ce5c308a7b96d95b8b43100b1f4d8ac4cabc7c3bef9fefd745.json b/.sqlx/query-a1256d6eec88a6ce5c308a7b96d95b8b43100b1f4d8ac4cabc7c3bef9fefd745.json new file mode 100644 index 0000000..baa9be3 --- /dev/null +++ b/.sqlx/query-a1256d6eec88a6ce5c308a7b96d95b8b43100b1f4d8ac4cabc7c3bef9fefd745.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "SELECT sf.id AS \"id!: i64\", sf.path AS \"path!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id\n JOIN movies m ON mf.owner_kind = 'movie' AND m.id = mf.owner_id\n WHERE m.root_id = ? AND sf.path IS NOT NULL\n ORDER BY sf.id", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "id" + } + } + }, + { + "name": "path!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true + ] + }, + "hash": "a1256d6eec88a6ce5c308a7b96d95b8b43100b1f4d8ac4cabc7c3bef9fefd745" +} diff --git a/.sqlx/query-89fb1aa140a11bcd9bb082b2dcba50df414a77bc191755d515e5d5905a651adc.json b/.sqlx/query-a2023f024d53aa5f139da347f22af38d57c6f846bb8eff74958fd4d1299268f8.json similarity index 68% rename from .sqlx/query-89fb1aa140a11bcd9bb082b2dcba50df414a77bc191755d515e5d5905a651adc.json rename to .sqlx/query-a2023f024d53aa5f139da347f22af38d57c6f846bb8eff74958fd4d1299268f8.json index ade8330..db86695 100644 --- a/.sqlx/query-89fb1aa140a11bcd9bb082b2dcba50df414a77bc191755d515e5d5905a651adc.json +++ b/.sqlx/query-a2023f024d53aa5f139da347f22af38d57c6f846bb8eff74958fd4d1299268f8.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT mf.id AS \"id!: i64\", mf.path AS \"path!: String\"\n FROM media_files mf\n JOIN episodes e ON mf.owner_kind = 'episode' AND e.id = mf.owner_id\n JOIN seasons se ON se.id = e.season_id\n WHERE se.series_id = ?", + "query": "SELECT mf.id AS \"id!: i64\", mf.path AS \"path!: String\"\n FROM media_files mf\n JOIN episodes e ON mf.owner_kind = 'episode' AND e.id = mf.owner_id\n JOIN seasons se ON se.id = e.season_id\n WHERE se.series_id = ?", "describe": { "columns": [ { @@ -34,5 +34,5 @@ false ] }, - "hash": "89fb1aa140a11bcd9bb082b2dcba50df414a77bc191755d515e5d5905a651adc" + "hash": "a2023f024d53aa5f139da347f22af38d57c6f846bb8eff74958fd4d1299268f8" } diff --git a/.sqlx/query-adf45101bc3ba20d35b59794053029e89d22ada5c7ffecd47187ec34261bc223.json b/.sqlx/query-adf45101bc3ba20d35b59794053029e89d22ada5c7ffecd47187ec34261bc223.json new file mode 100644 index 0000000..254a870 --- /dev/null +++ b/.sqlx/query-adf45101bc3ba20d35b59794053029e89d22ada5c7ffecd47187ec34261bc223.json @@ -0,0 +1,86 @@ +{ + "db_name": "SQLite", + "query": "SELECT media_file_id AS \"media_file_id!: i64\",\n language AS \"language!: String\",\n state AS \"state!: SubtitleState\",\n attempts AS \"attempts!: i64\",\n last_attempt_at,\n last_failure\n FROM subtitle_attempts\n WHERE media_file_id = ?\n ORDER BY language", + "describe": { + "columns": [ + { + "name": "media_file_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "media_file_id" + } + } + }, + { + "name": "language!: String", + "ordinal": 1, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "language" + } + } + }, + { + "name": "state!: SubtitleState", + "ordinal": 2, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "state" + } + } + }, + { + "name": "attempts!: i64", + "ordinal": 3, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "attempts" + } + } + }, + { + "name": "last_attempt_at", + "ordinal": 4, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "last_attempt_at" + } + } + }, + { + "name": "last_failure", + "ordinal": 5, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_attempts", + "name": "last_failure" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + true + ] + }, + "hash": "adf45101bc3ba20d35b59794053029e89d22ada5c7ffecd47187ec34261bc223" +} diff --git a/.sqlx/query-af10ccd655f39235f525a531e38850b2e60ff713a038ee1e7c04c616b4613b52.json b/.sqlx/query-af10ccd655f39235f525a531e38850b2e60ff713a038ee1e7c04c616b4613b52.json new file mode 100644 index 0000000..52b1bab --- /dev/null +++ b/.sqlx/query-af10ccd655f39235f525a531e38850b2e60ff713a038ee1e7c04c616b4613b52.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT probed FROM media_files WHERE id = ?", + "describe": { + "columns": [ + { + "name": "probed", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "probed" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true + ] + }, + "hash": "af10ccd655f39235f525a531e38850b2e60ff713a038ee1e7c04c616b4613b52" +} diff --git a/.sqlx/query-b2d937e5b8381de44fc6c1915e3deb24236c2c8afa25ffec1c7bbe519aac8803.json b/.sqlx/query-b2d937e5b8381de44fc6c1915e3deb24236c2c8afa25ffec1c7bbe519aac8803.json new file mode 100644 index 0000000..3e27f73 --- /dev/null +++ b/.sqlx/query-b2d937e5b8381de44fc6c1915e3deb24236c2c8afa25ffec1c7bbe519aac8803.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT sf.path AS \"path!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id\n JOIN episodes e ON mf.owner_kind = 'episode' AND e.id = mf.owner_id\n WHERE e.season_id = ? AND sf.path IS NOT NULL", + "describe": { + "columns": [ + { + "name": "path!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true + ] + }, + "hash": "b2d937e5b8381de44fc6c1915e3deb24236c2c8afa25ffec1c7bbe519aac8803" +} diff --git a/.sqlx/query-bc6dfe5a5cc639a64e3020c348a2dc74b4298101f8c29d9f8e6397cf79bb83d7.json b/.sqlx/query-bc6dfe5a5cc639a64e3020c348a2dc74b4298101f8c29d9f8e6397cf79bb83d7.json new file mode 100644 index 0000000..7c0680a --- /dev/null +++ b/.sqlx/query-bc6dfe5a5cc639a64e3020c348a2dc74b4298101f8c29d9f8e6397cf79bb83d7.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT providers_enabled AS \"providers_enabled!: String\"\n FROM subtitle_settings WHERE id = 1", + "describe": { + "columns": [ + { + "name": "providers_enabled!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "providers_enabled" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "bc6dfe5a5cc639a64e3020c348a2dc74b4298101f8c29d9f8e6397cf79bb83d7" +} diff --git a/.sqlx/query-c5a9e43cfeb315e70bbb603f22acc519d7df3cd0b4d606d0d9056414ac6c503b.json b/.sqlx/query-c5a9e43cfeb315e70bbb603f22acc519d7df3cd0b4d606d0d9056414ac6c503b.json new file mode 100644 index 0000000..c3fd760 --- /dev/null +++ b/.sqlx/query-c5a9e43cfeb315e70bbb603f22acc519d7df3cd0b4d606d0d9056414ac6c503b.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO subtitle_attempts (media_file_id, language, state)\n VALUES (?, ?, 'satisfied')\n ON CONFLICT (media_file_id, language) DO UPDATE\n SET state = 'satisfied',\n last_failure = NULL,\n updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "c5a9e43cfeb315e70bbb603f22acc519d7df3cd0b4d606d0d9056414ac6c503b" +} diff --git a/.sqlx/query-d81ec62e6c4879d08ce3551c0c71df012bed05ac2a37933adc2deb6552eeeac1.json b/.sqlx/query-d81ec62e6c4879d08ce3551c0c71df012bed05ac2a37933adc2deb6552eeeac1.json new file mode 100644 index 0000000..6057bf2 --- /dev/null +++ b/.sqlx/query-d81ec62e6c4879d08ce3551c0c71df012bed05ac2a37933adc2deb6552eeeac1.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT EXISTS(SELECT 1 FROM movies WHERE id = ?) AS \"exists!: bool\"", + "describe": { + "columns": [ + { + "name": "exists!: bool", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "d81ec62e6c4879d08ce3551c0c71df012bed05ac2a37933adc2deb6552eeeac1" +} diff --git a/.sqlx/query-e17b71f67d8b79c54d2d6f32d5e4197d77faac1763b360e8e609b0b9ba5820f1.json b/.sqlx/query-e17b71f67d8b79c54d2d6f32d5e4197d77faac1763b360e8e609b0b9ba5820f1.json new file mode 100644 index 0000000..b3a5a5a --- /dev/null +++ b/.sqlx/query-e17b71f67d8b79c54d2d6f32d5e4197d77faac1763b360e8e609b0b9ba5820f1.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT path AS \"path!: String\" FROM subtitle_files\n WHERE media_file_id = ? AND language = ? AND path IS NOT NULL", + "describe": { + "columns": [ + { + "name": "path!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + true + ] + }, + "hash": "e17b71f67d8b79c54d2d6f32d5e4197d77faac1763b360e8e609b0b9ba5820f1" +} diff --git a/.sqlx/query-e3b82dc7b2a3b28753c84864239699cb2007a8101cccc22d223d1f587a5533f8.json b/.sqlx/query-e3b82dc7b2a3b28753c84864239699cb2007a8101cccc22d223d1f587a5533f8.json new file mode 100644 index 0000000..f22d82c --- /dev/null +++ b/.sqlx/query-e3b82dc7b2a3b28753c84864239699cb2007a8101cccc22d223d1f587a5533f8.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT EXISTS(SELECT 1 FROM subtitle_files WHERE path = ?) AS \"exists!: bool\"", + "describe": { + "columns": [ + { + "name": "exists!: bool", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "e3b82dc7b2a3b28753c84864239699cb2007a8101cccc22d223d1f587a5533f8" +} diff --git a/.sqlx/query-e5b075d0b7fe5410d3e55d5daf7837d0a9398b1954e713e80a1d82c0a7e172a6.json b/.sqlx/query-e5b075d0b7fe5410d3e55d5daf7837d0a9398b1954e713e80a1d82c0a7e172a6.json new file mode 100644 index 0000000..23d18a9 --- /dev/null +++ b/.sqlx/query-e5b075d0b7fe5410d3e55d5daf7837d0a9398b1954e713e80a1d82c0a7e172a6.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT id AS \"id!: i64\" FROM subtitle_files WHERE path = ?", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "id" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "e5b075d0b7fe5410d3e55d5daf7837d0a9398b1954e713e80a1d82c0a7e172a6" +} diff --git a/.sqlx/query-e7a0291c2fb8cfe59a02ffef11d9657ea6a40e4079573f569573075a205c50e7.json b/.sqlx/query-e7a0291c2fb8cfe59a02ffef11d9657ea6a40e4079573f569573075a205c50e7.json new file mode 100644 index 0000000..c3abdb0 --- /dev/null +++ b/.sqlx/query-e7a0291c2fb8cfe59a02ffef11d9657ea6a40e4079573f569573075a205c50e7.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT translation_engine AS \"translation_engine: String\"\n FROM subtitle_settings WHERE id = 1", + "describe": { + "columns": [ + { + "name": "translation_engine: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_settings", + "name": "translation_engine" + } + } + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + true + ] + }, + "hash": "e7a0291c2fb8cfe59a02ffef11d9657ea6a40e4079573f569573075a205c50e7" +} diff --git a/.sqlx/query-e8066bed6fb53ff90d78f47bb1c9ce8160918d4b35764fd988e5e1dd6c101089.json b/.sqlx/query-e8066bed6fb53ff90d78f47bb1c9ce8160918d4b35764fd988e5e1dd6c101089.json new file mode 100644 index 0000000..ad138bf --- /dev/null +++ b/.sqlx/query-e8066bed6fb53ff90d78f47bb1c9ce8160918d4b35764fd988e5e1dd6c101089.json @@ -0,0 +1,21 @@ +{ + "db_name": "SQLite", + "query": "SELECT EXISTS(SELECT 1 FROM media_files WHERE id = ?) AS \"exists!: bool\"", + "describe": { + "columns": [ + { + "name": "exists!: bool", + "ordinal": 0, + "type_info": "Integer", + "origin": "Expression" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "e8066bed6fb53ff90d78f47bb1c9ce8160918d4b35764fd988e5e1dd6c101089" +} diff --git a/.sqlx/query-eab2c4e7c21cd5d22a583c36dd2d79590e9ce55801c4b87b8db1b90f7b317225.json b/.sqlx/query-eab2c4e7c21cd5d22a583c36dd2d79590e9ce55801c4b87b8db1b90f7b317225.json new file mode 100644 index 0000000..b52433c --- /dev/null +++ b/.sqlx/query-eab2c4e7c21cd5d22a583c36dd2d79590e9ce55801c4b87b8db1b90f7b317225.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE subtitle_settings SET\n wanted_languages = ?, providers_enabled = ?, translation_engine = ?,\n provider_daily_budgets = ?, translator_daily_budgets = ?,\n remote_command_timeout_seconds = ?,\n openai_base_url = ?, openai_model = ?,\n updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')\n WHERE id = 1", + "describe": { + "columns": [], + "parameters": { + "Right": 8 + }, + "nullable": [] + }, + "hash": "eab2c4e7c21cd5d22a583c36dd2d79590e9ce55801c4b87b8db1b90f7b317225" +} diff --git a/.sqlx/query-f4c3eb506289667ace56da5d28c046163d5f12bd1dca53d3d6fc559f6e9ce393.json b/.sqlx/query-f4c3eb506289667ace56da5d28c046163d5f12bd1dca53d3d6fc559f6e9ce393.json new file mode 100644 index 0000000..b5cd900 --- /dev/null +++ b/.sqlx/query-f4c3eb506289667ace56da5d28c046163d5f12bd1dca53d3d6fc559f6e9ce393.json @@ -0,0 +1,38 @@ +{ + "db_name": "SQLite", + "query": "SELECT mf.id AS \"media_file_id!: i64\", e.id AS \"episode_id!: i64\"\n FROM media_files mf\n JOIN episodes e ON mf.owner_kind = 'episode' AND e.id = mf.owner_id\n JOIN seasons se ON se.id = e.season_id\n WHERE se.series_id = ?\n ORDER BY mf.path", + "describe": { + "columns": [ + { + "name": "media_file_id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "id" + } + } + }, + { + "name": "episode_id!: i64", + "ordinal": 1, + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false + ] + }, + "hash": "f4c3eb506289667ace56da5d28c046163d5f12bd1dca53d3d6fc559f6e9ce393" +} diff --git a/.sqlx/query-fa35de1c7e62c68dd1049d7e7e08c825d3ca31eb631744a57594aa2fc08fb945.json b/.sqlx/query-fa35de1c7e62c68dd1049d7e7e08c825d3ca31eb631744a57594aa2fc08fb945.json new file mode 100644 index 0000000..05ce4ec --- /dev/null +++ b/.sqlx/query-fa35de1c7e62c68dd1049d7e7e08c825d3ca31eb631744a57594aa2fc08fb945.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT sf.path AS \"path!: String\"\n FROM subtitle_files sf\n JOIN media_files mf ON mf.id = sf.media_file_id\n WHERE mf.owner_kind = 'episode' AND mf.owner_id = ? AND sf.path IS NOT NULL", + "describe": { + "columns": [ + { + "name": "path!: String", + "ordinal": 0, + "type_info": "Text", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "path" + } + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true + ] + }, + "hash": "fa35de1c7e62c68dd1049d7e7e08c825d3ca31eb631744a57594aa2fc08fb945" +} diff --git a/.sqlx/query-fcba9e1f708e6bf2d8471d0c7d26be0b5ad8a30fa042434ffaaff8c69ccf31de.json b/.sqlx/query-fcba9e1f708e6bf2d8471d0c7d26be0b5ad8a30fa042434ffaaff8c69ccf31de.json new file mode 100644 index 0000000..be8bf64 --- /dev/null +++ b/.sqlx/query-fcba9e1f708e6bf2d8471d0c7d26be0b5ad8a30fa042434ffaaff8c69ccf31de.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT id AS \"id!: i64\" FROM media_files\n WHERE owner_kind = ? AND owner_id = ? ORDER BY path", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "id" + } + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false + ] + }, + "hash": "fcba9e1f708e6bf2d8471d0c7d26be0b5ad8a30fa042434ffaaff8c69ccf31de" +} diff --git a/.sqlx/query-fe15c4a3774e663b03455300d0baec1dadc6cf995c20df803bc4bf49d1cc7c77.json b/.sqlx/query-fe15c4a3774e663b03455300d0baec1dadc6cf995c20df803bc4bf49d1cc7c77.json new file mode 100644 index 0000000..f8343ca --- /dev/null +++ b/.sqlx/query-fe15c4a3774e663b03455300d0baec1dadc6cf995c20df803bc4bf49d1cc7c77.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT id AS \"id!: i64\" FROM subtitle_files\n WHERE media_file_id = ? AND language = ? AND origin = 'embedded'\n AND forced = ? AND sdh = ?", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer", + "origin": { + "Table": { + "table": "subtitle_files", + "name": "id" + } + } + } + ], + "parameters": { + "Right": 4 + }, + "nullable": [ + false + ] + }, + "hash": "fe15c4a3774e663b03455300d0baec1dadc6cf995c20df803bc4bf49d1cc7c77" +} diff --git a/CLAUDE.md b/CLAUDE.md index db766c0..906b5cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,7 @@ crates/ ├── arr-indexer/ Torznab via Prowlarr ├── arr-dl/ Transmission RPC ├── arr-probe/ ffprobe wrapper +├── arr-subs/ subtitle providers, translation, sync ├── arr-db/ sqlx + migrations ├── arr-api/ axum + OpenAPI ├── arr-compat/ Radarr/Sonarr v3 shim for Jellyseerr diff --git a/Cargo.lock b/Cargo.lock index 78bdfe6..212d713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.5" @@ -35,6 +41,7 @@ dependencies = [ "arr-indexer", "arr-meta", "arr-parse", + "arr-subs", "axum", "chrono", "reqwest", @@ -88,6 +95,7 @@ dependencies = [ "arr-meta", "arr-parse", "arr-probe", + "arr-subs", "axum", "base64", "chrono", @@ -139,12 +147,14 @@ dependencies = [ name = "arr-e2e" version = "0.1.0" dependencies = [ + "arr-db", "arr-dl", "arr-indexer", "arr-meta", "chrono", "reqwest", "serde_json", + "sqlx", "tempfile", "tokio", "uuid", @@ -194,11 +204,31 @@ dependencies = [ "arr-core", "serde", "serde_json", + "tempfile", "thiserror", "tokio", "tracing", ] +[[package]] +name = "arr-subs" +version = "0.1.0" +dependencies = [ + "arr-core", + "arr-parse", + "chardetng", + "encoding_rs", + "reqwest", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", + "tracing", + "wiremock", + "zip", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -366,6 +396,17 @@ dependencies = [ "rand_core", ] +[[package]] +name = "chardetng" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13de944a44b5064ee5d3a5ceccc49a41bfec50f2580e66f82e87703acdb88b53" +dependencies = [ + "cfg-if", + "encoding_rs", + "memchr", +] + [[package]] name = "chrono" version = "0.4.45" @@ -423,6 +464,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" +[[package]] +name = "crc32fast" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-queue" version = "0.3.13" @@ -531,6 +581,15 @@ dependencies = [ "serde", ] +[[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 = "equivalent" version = "1.0.2" @@ -579,6 +638,16 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "miniz_oxide", + "zlib-rs", +] + [[package]] name = "flume" version = "0.12.0" @@ -1228,6 +1297,16 @@ dependencies = [ "unicase", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.2" @@ -1746,6 +1825,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "slab" version = "0.4.12" @@ -2305,6 +2390,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.20.1" @@ -2814,6 +2905,25 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "crc32fast", + "flate2", + "indexmap", + "memchr", + "typed-path", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 1e41ea8..e849805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ arr-indexer = { path = "crates/arr-indexer" } arr-meta = { path = "crates/arr-meta" } arr-parse = { path = "crates/arr-parse" } arr-probe = { path = "crates/arr-probe" } +arr-subs = { path = "crates/arr-subs" } # Runtime and HTTP axum = "0.8" @@ -47,13 +48,16 @@ quick-xml = { version = "0.37", features = ["serialize"] } toml = "0.8" # Odds and ends +chardetng = "1" chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] } clap = { version = "4.5", features = ["derive", "env"] } +encoding_rs = "0.8" thiserror = "2" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } url = "2" uuid = { version = "1", features = ["v4", "serde"] } +zip = { version = "8", default-features = false, features = ["deflate-flate2-zlib-rs"] } # Test-only tempfile = "3" diff --git a/DESIGN.md b/DESIGN.md index 4bb950c..dcd931c 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -780,6 +780,7 @@ arr-meta TMDB client arr-indexer Torznab via Prowlarr arr-dl Transmission RPC arr-probe ffprobe wrapper +arr-subs subtitle providers, translation, sync arr-db sqlx + migrations arr-api axum + OpenAPI arr-compat Radarr/Sonarr v3 shim for Jellyseerr @@ -862,7 +863,7 @@ previous one. grabbing, derived status. 7. **Owners and notifications** — tags, per-person ntfy topics, filtered views. 8. **Jellyseerr compat** — `arr-compat`. -9. **Subtitles** — replaces Bazarr. Out of scope for this document. +9. **Subtitles** — replaces Bazarr. See §15. Movies before TV because TV adds season packs, air-date calendars and per-episode state on top of an otherwise identical pipeline. Doing it second @@ -882,3 +883,106 @@ means that pipeline is already proven. RSS lane obeys the same guard — it skips packs for any season with episodes on disk. Whether a re-grab is ever wanted remains unresolved and deliberately deferred. + +## 15. Subtitles + +Replaces Bazarr. Phase 9 in §13; the `arr-subs` crate in §11. + +**Wanted set.** Global, not per root. Two languages are separately wanted for +every media file: Portuguese — pt-PT preferred, pt-BR accepted — and English. +A file is satisfied for a language when a subtitle in it exists, embedded or as +a sidecar. This is deliberately unlike §5.2's audio rules, which attach to a +root: subtitles carry no blacklist there and none here. pt-BR subtitles are +always fine. + +**Embedded tracks.** An embedded subtitle track satisfies its language. +Text-format tracks (`subrip`, `ass`, `mov_text`) are additionally extracted to +a sidecar SRT, because an extracted track is a legal translation source. +Image-format tracks (PGS on BluRay, VobSub on DVD) carry bitmaps, not text: +they satisfy viewing but can never feed a translator, and arr does not OCR +them. `arr-probe` already reports subtitle tracks with resolved languages; the +format is the new fact it must carry. + +**Providers.** OpenSubtitles.com and Podnapisi, behind one trait. + +**Ranking.** A `moviehash` match wins outright. Then an exact release-name +match, then same release group or same source, then uploader rating and +download count as tiebreakers. Every rejected candidate names the rule that +killed it, so the manual view described in §9.3 works unchanged for subtitles. + +**Forced and SDH.** A forced track covers only foreign-language lines and +on-screen signs; it never satisfies a want and arr never goes looking for one. +An SDH track is complete and satisfies, ranked below a plain subtitle. + +Neither gets its own sidecar name. A language is satisfied by exactly one +sidecar, so `