59 Commits

Author SHA1 Message Date
dependabot[bot]
b056218619 Bump aiohttp from 3.14.3 to 3.14.4
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.14.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-11 06:53:30 +00:00
√(noham)²
45c78ac87b Merge pull request #42 from NohamR/dependabot/pip/python-dotenv-1.2.4
Bump python-dotenv from 1.2.3 to 1.2.4
2026-10-04 17:06:17 +02:00
dependabot[bot]
7c566a24a9 Bump python-dotenv from 1.2.3 to 1.2.4
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v1.2.3...v1.2.4)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-version: 1.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-04 06:52:50 +00:00
√(noham)²
3732b57562 Merge pull request #39 from NohamR/dependabot/pip/tqdm-4.70.1
Bump tqdm from 4.70.0 to 4.70.1
2026-09-20 21:17:50 +02:00
√(noham)²
ea0223c94a Merge branch 'main' into dependabot/pip/tqdm-4.70.1 2026-09-20 21:17:38 +02:00
√(noham)²
44742ecc1e Merge pull request #40 from NohamR/dependabot/uv/tzdata-2026.4
Bump tzdata from 2026.3 to 2026.4
2026-09-20 21:17:18 +02:00
√(noham)²
88e0f558e2 Merge branch 'main' into dependabot/uv/tzdata-2026.4 2026-09-20 21:17:08 +02:00
√(noham)²
80565f70fe Merge pull request #41 from NohamR/dependabot/uv/tqdm-4.70.1
Bump tqdm from 4.70.0 to 4.70.1
2026-09-20 21:16:33 +02:00
dependabot[bot]
453929c9d9 Bump tqdm from 4.70.0 to 4.70.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.70.0 to 4.70.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.70.0...v4.70.1)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.70.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-20 06:53:03 +00:00
dependabot[bot]
c682d46b0f Bump tzdata from 2026.3 to 2026.4
Bumps [tzdata](https://github.com/python/tzdata) from 2026.3 to 2026.4.
- [Release notes](https://github.com/python/tzdata/releases)
- [Changelog](https://github.com/python/tzdata/blob/master/NEWS.md)
- [Commits](https://github.com/python/tzdata/compare/2026.3...2026.4)

---
updated-dependencies:
- dependency-name: tzdata
  dependency-version: '2026.4'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-20 06:52:47 +00:00
dependabot[bot]
749e19e5c5 Bump tqdm from 4.70.0 to 4.70.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.70.0 to 4.70.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.70.0...v4.70.1)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.70.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-20 06:52:39 +00:00
√(noham)²
455e967196 Merge pull request #37 from NohamR/dependabot/uv/soupsieve-2.9
Bump soupsieve from 2.8.4 to 2.9
2026-09-18 13:29:57 +02:00
dependabot[bot]
392ac6d3aa Bump soupsieve from 2.8.4 to 2.9
Bumps [soupsieve](https://github.com/facelessuser/soupsieve) from 2.8.4 to 2.9.
- [Release notes](https://github.com/facelessuser/soupsieve/releases)
- [Commits](https://github.com/facelessuser/soupsieve/compare/2.8.4...2.9)

---
updated-dependencies:
- dependency-name: soupsieve
  dependency-version: '2.9'
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-18 02:03:47 +00:00
√(noham)²
e12c6f47a3 Merge pull request #36 from NohamR/dependabot/pip/python-dotenv-1.2.3
Bump python-dotenv from 1.2.2 to 1.2.3
2026-08-23 14:08:49 +02:00
dependabot[bot]
f78bf1d6c0 Bump python-dotenv from 1.2.2 to 1.2.3
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v1.2.2...v1.2.3)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-version: 1.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-23 06:52:30 +00:00
√(noham)²
b7efe34ed0 Merge branch 'main' of https://github.com/NohamR/OqeeRewind 2026-08-13 17:40:09 +02:00
√(noham)²
ae5035c6a8 optimize DASH probing and segment download paths
- Switch bruteforce probes from GET to HEAD to avoid downloading
  full segment bodies (only the status code is needed)
- Cap bruteforce concurrency with a semaphore (default 100) instead of issuing one task per tick, and cancel leftover tasks on early exit
- Reuse a single aiohttp session / event loop for the manifest mode's AVC/HEVC/audio bruteforces to keep TCP connections alive
- Hoist the per-request segment headers into a module-level constant
- Use a set for the already-downloaded resume check in save_segments
- Replace read()-into-memory copies with shutil.copyfileobj in   merge_segments and decrypt
2026-08-13 17:40:06 +02:00
√(noham)²
cada374158 Merge pull request #32 from NohamR/dependabot/pip/tqdm-4.70.0
Bump tqdm from 4.69.1 to 4.70.0
2026-08-02 13:52:10 +02:00
√(noham)²
daebae4411 Merge pull request #33 from NohamR/dependabot/pip/prompt-toolkit-3.0.53
Bump prompt-toolkit from 3.0.52 to 3.0.53
2026-08-02 13:52:01 +02:00
√(noham)²
86e370b86d Merge pull request #34 from NohamR/dependabot/uv/tqdm-4.70.0
Bump tqdm from 4.69.1 to 4.70.0
2026-08-02 13:51:52 +02:00
√(noham)²
ee5ba75a50 Merge pull request #35 from NohamR/dependabot/uv/prompt-toolkit-3.0.53
Bump prompt-toolkit from 3.0.52 to 3.0.53
2026-08-02 13:51:40 +02:00
dependabot[bot]
94e7b71bf6 Bump prompt-toolkit from 3.0.52 to 3.0.53
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.52 to 3.0.53.
- [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases)
- [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/main/CHANGELOG)
- [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.52...3.0.53)

---
updated-dependencies:
- dependency-name: prompt-toolkit
  dependency-version: 3.0.53
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 06:53:06 +00:00
dependabot[bot]
03549172f0 Bump tqdm from 4.69.1 to 4.70.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.69.1 to 4.70.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.69.1...v4.70.0)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.70.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 06:52:58 +00:00
dependabot[bot]
cbb6275aff Bump prompt-toolkit from 3.0.52 to 3.0.53
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.52 to 3.0.53.
- [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases)
- [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/main/CHANGELOG)
- [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.52...3.0.53)

---
updated-dependencies:
- dependency-name: prompt-toolkit
  dependency-version: 3.0.53
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 06:52:44 +00:00
dependabot[bot]
7f465affbb Bump tqdm from 4.69.1 to 4.70.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.69.1 to 4.70.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.69.1...v4.70.0)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.70.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-02 06:52:39 +00:00
√(noham)²
4524d474eb Merge pull request #29 from NohamR/dependabot/pip/tqdm-4.69.1
Bump tqdm from 4.69.0 to 4.69.1
2026-07-28 23:24:07 +02:00
√(noham)²
03512e4430 Merge pull request #30 from NohamR/dependabot/pip/aiohttp-3.14.3
Bump aiohttp from 3.14.1 to 3.14.3
2026-07-28 23:23:53 +02:00
√(noham)²
619c91b71d Merge pull request #31 from NohamR/dependabot/uv/tqdm-4.69.1
Bump tqdm from 4.69.0 to 4.69.1
2026-07-28 23:23:34 +02:00
dependabot[bot]
9f97f7ff33 Bump tqdm from 4.69.0 to 4.69.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.69.0 to 4.69.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.69.0...v4.69.1)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.69.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-26 06:52:58 +00:00
dependabot[bot]
5634871a35 Bump aiohttp from 3.14.1 to 3.14.3
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.14.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-26 06:52:49 +00:00
dependabot[bot]
0063753c47 Bump tqdm from 4.69.0 to 4.69.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.69.0 to 4.69.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.69.0...v4.69.1)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.69.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-26 06:52:31 +00:00
√(noham)²
586a45aeda Merge pull request #28 from NohamR/dependabot/uv/tqdm-4.69.0
Bump tqdm from 4.68.4 to 4.69.0
2026-07-19 19:26:40 +02:00
√(noham)²
e26b5c7fe1 Merge pull request #27 from NohamR/dependabot/pip/tqdm-4.69.0
Bump tqdm from 4.68.4 to 4.69.0
2026-07-19 19:26:32 +02:00
dependabot[bot]
34219bacfb Bump tqdm from 4.68.4 to 4.69.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.68.4 to 4.69.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.68.4...v4.69.0)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.69.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-19 06:52:52 +00:00
dependabot[bot]
c647d510c8 Bump tqdm from 4.68.4 to 4.69.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.68.4 to 4.69.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.68.4...v4.69.0)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.69.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-19 06:52:31 +00:00
√(noham)²
a57f0a74a1 Merge branch 'main' of https://github.com/NohamR/OqeeRewind 2026-07-12 12:30:59 +02:00
√(noham)²
f2e41bb8d6 Update requirements.txt 2026-07-12 12:30:35 +02:00
√(noham)²
e6dcdd90c3 Merge pull request #25 from NohamR/dependabot/uv/tzdata-2026.3
Bump tzdata from 2026.2 to 2026.3
2026-07-12 12:26:02 +02:00
√(noham)²
3c3b7f5202 Merge branch 'main' into dependabot/uv/tzdata-2026.3 2026-07-12 12:25:33 +02:00
√(noham)²
489b95c7b5 Merge pull request #26 from NohamR/dependabot/uv/tqdm-4.68.4
Bump tqdm from 4.68.3 to 4.68.4
2026-07-12 12:24:12 +02:00
√(noham)²
8fbc0f8678 Merge pull request #24 from NohamR/dependabot/pip/tqdm-4.68.4
Bump tqdm from 4.68.3 to 4.68.4
2026-07-12 12:23:47 +02:00
dependabot[bot]
11178ac858 Bump tqdm from 4.68.3 to 4.68.4
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.68.3 to 4.68.4.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.68.3...v4.68.4)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.68.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-12 06:53:01 +00:00
dependabot[bot]
5f80e8be97 Bump tzdata from 2026.2 to 2026.3
Bumps [tzdata](https://github.com/python/tzdata) from 2026.2 to 2026.3.
- [Release notes](https://github.com/python/tzdata/releases)
- [Changelog](https://github.com/python/tzdata/blob/master/NEWS.md)
- [Commits](https://github.com/python/tzdata/compare/2026.2...2026.3)

---
updated-dependencies:
- dependency-name: tzdata
  dependency-version: '2026.3'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-12 06:52:38 +00:00
dependabot[bot]
14eea661a3 Bump tqdm from 4.68.3 to 4.68.4
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.68.3 to 4.68.4.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.68.3...v4.68.4)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.68.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-12 06:52:30 +00:00
√(noham)²
cbb4a2ad63 Merge pull request #20 from NohamR/dependabot/pip/aiohttp-3.14.1
Bump aiohttp from 3.13.5 to 3.14.1
2026-06-28 14:48:42 +02:00
√(noham)²
a8f7f91305 Merge pull request #21 from NohamR/dependabot/pip/python-dotenv-1.2.2
Bump python-dotenv from 1.2.1 to 1.2.2
2026-06-28 14:48:33 +02:00
√(noham)²
6027190194 Merge pull request #22 from NohamR/dependabot/pip/requests-2.34.2
Bump requests from 2.32.5 to 2.34.2
2026-06-28 14:48:22 +02:00
√(noham)²
f7729dcd2f Merge branch 'main' into dependabot/pip/requests-2.34.2 2026-06-28 14:48:13 +02:00
√(noham)²
0054e70257 Merge pull request #23 from NohamR/dependabot/pip/tabulate-0.10.0
Bump tabulate from 0.9.0 to 0.10.0
2026-06-28 14:47:40 +02:00
dependabot[bot]
b566360abf Bump tabulate from 0.9.0 to 0.10.0
Bumps [tabulate](https://github.com/astanin/python-tabulate) from 0.9.0 to 0.10.0.
- [Changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG)
- [Commits](https://github.com/astanin/python-tabulate/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: tabulate
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 06:52:53 +00:00
dependabot[bot]
c534ba9357 Bump requests from 2.32.5 to 2.34.2
Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.34.2.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.34.2)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.34.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 06:52:50 +00:00
dependabot[bot]
3336477824 Bump python-dotenv from 1.2.1 to 1.2.2
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-version: 1.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 06:52:43 +00:00
dependabot[bot]
67986279f1 Bump aiohttp from 3.13.5 to 3.14.1
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.14.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 06:52:39 +00:00
√(noham)²
ff8462d3df Times + logging changes 2026-06-23 18:31:50 +02:00
√(noham)²
51a58d3694 Add tzdata 2026-06-22 11:17:16 +02:00
√(noham)²
4d613a1b3f Merge pull request #18 from NohamR/dependabot/pip/tqdm-4.68.3
Bump tqdm from 4.68.2 to 4.68.3
2026-06-21 14:49:42 +02:00
√(noham)²
cd5c19793f Merge pull request #19 from NohamR/dependabot/uv/tqdm-4.68.3
Bump tqdm from 4.68.2 to 4.68.3
2026-06-21 14:49:30 +02:00
dependabot[bot]
297e8d7f59 Bump tqdm from 4.68.2 to 4.68.3
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.68.2 to 4.68.3.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.68.2...v4.68.3)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.68.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-21 06:52:30 +00:00
dependabot[bot]
9d0da79cf0 Bump tqdm from 4.68.2 to 4.68.3
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.68.2 to 4.68.3.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.68.2...v4.68.3)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.68.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-21 06:52:27 +00:00
8 changed files with 194 additions and 136 deletions

81
main.py
View File

@@ -9,6 +9,7 @@ import shutil
import logging
from datetime import datetime, timedelta
from dotenv import load_dotenv
import aiohttp
from utils.input import (
stream_selection,
get_date_input,
@@ -26,6 +27,7 @@ from utils.times import (
convert_sec_to_date,
find_nearest_tick_by_hour,
bruteforce,
TIMESCALE,
)
from utils.stream import (
save_segments,
@@ -38,7 +40,6 @@ from utils.stream import (
from utils.logging_config import setup_logging, logger
load_dotenv()
TIMESCALE = 90000
DURATION = 288000
@@ -71,7 +72,7 @@ def parse_arguments():
parser.add_argument(
"--title",
type=str,
default="title",
default=None,
help="Title for the download (default: channel_id_start_date)",
)
parser.add_argument("--username", type=str, help="Oqee username for authentication")
@@ -190,45 +191,21 @@ if __name__ == "__main__":
mpd_content = get_manifest(dash_id)
manifest_info = parse_mpd_manifest(mpd_content)
avc_tick = None
avc_track_id = None
if "video" in selections_avc:
avc_sel = selections_avc["video"]
avc_init_segment = avc_sel["segments"]["initialization"]
avc_track_id = avc_init_segment.split("/")[-1].split("_init")[0]
logger.info("Bruteforcing AVC video track %s...", avc_track_id)
avc_valid_ticks = asyncio.run(
bruteforce(avc_track_id, start_tick_user, batch_size)
)
if len(avc_valid_ticks) == 0:
logger.warning(
"No valid ticks found for AVC video. AVC tracks will be removed from manifest."
)
else:
avc_tick = avc_valid_ticks[0]
logger.info("AVC video tick found: %s", avc_tick)
else:
logger.warning(
"No AVC video track available. AVC tracks will be removed from manifest."
)
hevc_tick = None
hevc_track_id = None
if "video" in selections_hevc:
hevc_sel = selections_hevc["video"]
hevc_init_segment = hevc_sel["segments"]["initialization"]
hevc_track_id = hevc_init_segment.split("/")[-1].split("_init")[0]
logger.info("Bruteforcing HEVC video track %s...", hevc_track_id)
hevc_valid_ticks = asyncio.run(
bruteforce(hevc_track_id, start_tick_user, batch_size)
)
if len(hevc_valid_ticks) == 0:
logger.warning(
"No valid ticks found for HEVC video. HEVC tracks will be removed from manifest."
)
else:
hevc_tick = hevc_valid_ticks[0]
logger.info("HEVC video tick found: %s", hevc_tick)
else:
logger.warning(
"No HEVC video track available. HEVC tracks will be removed from manifest."
@@ -239,10 +216,52 @@ if __name__ == "__main__":
audio_init_segment = audio_sel["segments"]["initialization"]
audio_track_id = audio_init_segment.split("/")[-1].split("_init")[0]
logger.info("Bruteforcing audio track %s...", audio_track_id)
audio_valid_ticks = asyncio.run(
bruteforce(audio_track_id, start_tick_user, batch_size)
async def bruteforce_all():
async with aiohttp.ClientSession() as session:
avc_ticks = None
hevc_ticks = None
audio_ticks = []
if avc_track_id:
logger.info(
"Bruteforcing AVC video track %s...", avc_track_id
)
avc_valid = await bruteforce(
avc_track_id, start_tick_user, batch_size, session=session
)
if len(avc_valid) == 0:
logger.warning(
"No valid ticks found for AVC video. AVC tracks will be removed from manifest."
)
else:
avc_ticks = avc_valid[0]
logger.info("AVC video tick found: %s", avc_ticks)
if hevc_track_id:
logger.info(
"Bruteforcing HEVC video track %s...", hevc_track_id
)
hevc_valid = await bruteforce(
hevc_track_id, start_tick_user, batch_size, session=session
)
if len(hevc_valid) == 0:
logger.warning(
"No valid ticks found for HEVC video. HEVC tracks will be removed from manifest."
)
else:
hevc_ticks = hevc_valid[0]
logger.info("HEVC video tick found: %s", hevc_ticks)
logger.info("Bruteforcing audio track %s...", audio_track_id)
audio_valid = await bruteforce(
audio_track_id, start_tick_user, batch_size, session=session
)
if len(audio_valid) > 0:
audio_ticks = audio_valid
return avc_ticks, hevc_ticks, audio_ticks
avc_tick, hevc_tick, audio_valid_ticks = asyncio.run(bruteforce_all())
if len(audio_valid_ticks) == 0:
logger.error("No valid ticks found for audio.")
sys.exit(1)

View File

@@ -17,7 +17,8 @@ dependencies = [
"pywidevine>=1.9.0",
"requests>=2.32.5",
"tabulate>=0.9.0",
"tqdm>=4.68.2",
"tqdm>=4.70.0",
"tzdata>=2026.3",
]
[project.scripts]

View File

@@ -1,7 +1,10 @@
aiohttp==3.13.5
aiohttp==3.14.4
beautifulsoup4==4.15.0
InquirerPy==0.3.4
python-dotenv==1.2.1
prompt_toolkit==3.0.53
python-dotenv==1.2.4
pywidevine==1.9.0
Requests==2.32.5
tabulate==0.9.0
tqdm==4.68.2
Requests==2.34.2
tabulate==0.10.0
tqdm==4.70.1
tzdata==2026.4

View File

@@ -9,6 +9,7 @@ from InquirerPy.base.control import Choice
from utils.stream import get_manifest, parse_mpd_manifest, organize_by_content_type
from utils.logging_config import logger
from utils.times import FRANCE_TZ
SERVICE_PLAN_API_URL = "https://api.oqee.net/api/v6/service_plan"
EPG_API_URL = "https://api.oqee.net/api/v1/epg/all/{unix}"
@@ -484,7 +485,7 @@ def get_epg_data_at(dt: datetime.datetime):
else:
dt_aligned = dt.replace(minute=0, second=0, microsecond=0)
unix_time = int(dt_aligned.timestamp())
unix_time = int(dt_aligned.replace(tzinfo=FRANCE_TZ).timestamp())
logger.info("Fetching EPG for aligned time: %s (unix=%d)", dt_aligned, unix_time)
try:
@@ -533,8 +534,8 @@ def select_program_from_epg(programs, original_start_date, original_end_date):
# Extract the live data from the program
live_data = program.get("live", program)
title = live_data.get("title", "Untitled")
start_time = datetime.datetime.fromtimestamp(live_data.get("start", 0))
end_time = datetime.datetime.fromtimestamp(live_data.get("end", 0))
start_time = datetime.datetime.fromtimestamp(live_data.get("start", 0), tz=FRANCE_TZ).replace(tzinfo=None)
end_time = datetime.datetime.fromtimestamp(live_data.get("end", 0), tz=FRANCE_TZ).replace(tzinfo=None)
duration_min = (end_time - start_time).total_seconds() / 60
choice_name = (
@@ -586,8 +587,8 @@ def select_program_from_epg(programs, original_start_date, original_end_date):
# Extract live data and convert program timestamps to datetime objects
live_data = selected_program.get("live", selected_program)
program_start = datetime.datetime.fromtimestamp(live_data.get("start", 0))
program_end = datetime.datetime.fromtimestamp(live_data.get("end", 0))
program_start = datetime.datetime.fromtimestamp(live_data.get("start", 0), tz=FRANCE_TZ).replace(tzinfo=None)
program_end = datetime.datetime.fromtimestamp(live_data.get("end", 0), tz=FRANCE_TZ).replace(tzinfo=None)
program_title = live_data.get("title", "Untitled")
logger.info("Selected program:")

View File

@@ -18,6 +18,16 @@ ET.register_namespace("", "urn:mpeg:dash:schema:mpd:2011")
ET.register_namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
ET.register_namespace("cenc", "urn:mpeg:cenc:2013")
SEGMENT_HEADERS = {
"Accept": "*/*",
"Referer": "https://tv.free.fr/",
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/143.0.0.0 Safari/537.36"
),
}
def parse_mpd_manifest(mpd_content: str) -> Dict[str, Any]:
"""Parse an MPD manifest and extract metadata.
@@ -340,7 +350,7 @@ def get_manifest(manifest_id):
async def fetch_segment(session, ticks, track_id):
"""Fetch a media segment asynchronously.
"""Probe whether a media segment exists using a lightweight HEAD request.
Args:
session: The aiohttp ClientSession.
@@ -348,20 +358,11 @@ async def fetch_segment(session, ticks, track_id):
track_id: The track identifier.
Returns:
The tick value if successful, None otherwise.
The tick value if the segment exists, None otherwise.
"""
url = f"https://media4.stream.proxad.net/media/{track_id}_{ticks}"
headers = {
"Accept": "*/*",
"Referer": "https://tv.free.fr/",
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/143.0.0.0 Safari/537.36"
),
}
try:
async with session.get(url, headers=headers) as resp:
async with session.head(url, headers=SEGMENT_HEADERS) as resp:
if resp.status == 200:
return ticks
return None
@@ -377,16 +378,7 @@ def get_init(output_folder, track_id):
track_id: The track identifier.
"""
url = f"https://media4.stream.proxad.net/media/{track_id}_init"
headers = {
"Accept": "*/*",
"Referer": "https://tv.free.fr/",
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/143.0.0.0 Safari/537.36"
),
}
response = requests.get(url, headers=headers, timeout=10)
response = requests.get(url, headers=SEGMENT_HEADERS, timeout=10)
if response.status_code == 200:
os.makedirs(f"{output_folder}/segments_{track_id}", exist_ok=True)
init_path = f"{output_folder}/segments_{track_id}/init.mp4"
@@ -418,17 +410,8 @@ async def save_segments(
Tuple of (success: bool, tick: int, rep: int)
"""
url = f"https://media4.stream.proxad.net/media/{track_id}_{tick}"
headers = {
"Accept": "*/*",
"Referer": "https://tv.free.fr/",
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/143.0.0.0 Safari/537.36"
),
}
try:
async with session.get(url, headers=headers) as resp:
async with session.get(url, headers=SEGMENT_HEADERS) as resp:
if resp.status == 200:
content = await resp.read()
filename = f"{output_folder}/segments_{track_id}/{tick}.m4s"
@@ -458,11 +441,11 @@ async def save_segments(
segments_to_download = [(start_tick + i * duration, i) for i in range(rep_nb)]
# In case of resuming, check which segments are already downloaded
already_downloaded = [
already_downloaded = {
int(f.split(".")[0])
for f in os.listdir(f"{output_folder}/segments_{track_id}")
if f.endswith(".m4s") and f.split(".")[0].isdigit()
]
}
segments_to_download = [
(tick, rep) for tick, rep in segments_to_download if tick not in already_downloaded
]

View File

@@ -12,6 +12,7 @@ from utils.stream import fetch_segment
from utils.logging_config import logger
FRANCE_TZ = ZoneInfo("Europe/Paris")
TIMESCALE = 90000
def convert_ticks_to_sec(ticks, timescale):
@@ -54,23 +55,39 @@ def future(rep, base, duration):
return base + rep * duration
async def bruteforce(track_id, date, batch_size=20000):
"""Bruteforce segments to find valid ticks."""
async def bruteforce(track_id, date, batch_size=20000, concurrency=100, session=None):
"""Bruteforce segments to find valid ticks.
Args:
track_id: The track identifier.
date: The base tick to start probing from.
batch_size: Number of ticks to probe per batch.
concurrency: Maximum number of in-flight requests at once.
session: Optional shared aiohttp session. A new one is created if omitted.
Returns:
List of valid ticks found (empty if none).
"""
valid_ticks = []
total_requests = 288000
logger.debug("Starting bruteforce for %s near %s", track_id, date)
logger.debug("Starting bruteforce for %s near (%s) %s", track_id, date, convert_sec_to_date(convert_ticks_to_sec(date, TIMESCALE)))
start_time = time.time()
owns_session = session is None
if owns_session:
session = aiohttp.ClientSession()
try:
async with aiohttp.ClientSession() as session:
semaphore = asyncio.Semaphore(concurrency)
async def probe(t):
async with semaphore:
return await fetch_segment(session, t + date, track_id)
for batch_start in range(0, total_requests, batch_size):
batch_end = min(batch_start + batch_size, total_requests)
tasks = [
fetch_segment(session, t + date, track_id)
for t in range(batch_start, batch_end)
]
tasks = [probe(t) for t in range(batch_start, batch_end)]
results = []
for coro in tqdm(
@@ -89,10 +106,15 @@ async def bruteforce(track_id, date, batch_size=20000):
# Stop if we found valid ticks
if valid_ticks:
logger.debug("Found valid ticks: %s, stopping bruteforce.", valid_ticks)
for task in tasks:
task.cancel()
break
except KeyboardInterrupt:
logger.error("Interrupted by user (Ctrl+C)")
finally:
if owns_session:
await session.close()
elapsed = time.time() - start_time
logger.debug("Completed in %.2fs", elapsed)

View File

@@ -39,7 +39,7 @@ def merge_segments(input_folder: str, track_id: str, output_file: str):
with open(output_file, "wb") as outfile:
for fname in segment_files:
with open(f"{segment_folder}/{fname}", "rb") as infile:
outfile.write(infile.read())
shutil.copyfileobj(infile, outfile)
logger.info("Merged segments into %s", output_file)
@@ -104,7 +104,7 @@ def decrypt(segment_dir, init_path, output_file, key):
with open(chunk_merged, "wb") as outfile:
for fname in chunk_files:
with open(os.path.join(segment_dir, fname), "rb") as infile:
outfile.write(infile.read())
shutil.copyfileobj(infile, outfile)
logger.debug("Decrypting chunk %d", chunk_num)
result = subprocess.run(

91
uv.lock generated
View File

@@ -175,7 +175,7 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.10'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/18/74/dfb75f9ccd592bbedb175d4a32fc643cf569d7c218508bfbd6ea7ef9c091/astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", size = 400439, upload-time = "2025-07-13T18:04:23.177Z" }
wheels = [
@@ -192,7 +192,7 @@ resolution-markers = [
"python_full_version == '3.10.*'",
]
dependencies = [
{ name = "typing-extensions", marker = "python_full_version == '3.10.*'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b7/22/97df040e15d964e592d3a180598ace67e91b7c559d8298bdb3c949dc6e42/astroid-4.0.2.tar.gz", hash = "sha256:ac8fb7ca1c08eb9afec91ccc23edbd8ac73bb22cbdd7da1d488d9fb8d6579070", size = 405714, upload-time = "2025-11-09T21:21:18.373Z" }
wheels = [
@@ -217,10 +217,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
]
[[package]]
name = "beautifulsoup4"
version = "4.6.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.10'",
]
sdist = { url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz", hash = "sha256:90f8e61121d6ae58362ce3bed8cd997efb00c914eae0ff3d363c32f9a9822d10", size = 167469, upload-time = "2018-08-12T16:39:49.655Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/21/0a/47fdf541c97fd9b6a610cb5fd518175308a7cc60569962e776ac52420387/beautifulsoup4-4.6.3-py3-none-any.whl", hash = "sha256:194ec62a25438adcb3fdb06378b26559eda1ea8a747367d34c33cef9c7f48d57", size = 90375, upload-time = "2018-08-12T16:39:48.02Z" },
]
[[package]]
name = "beautifulsoup4"
version = "4.14.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
"python_full_version == '3.11.*'",
"python_full_version == '3.10.*'",
]
dependencies = [
{ name = "soupsieve" },
{ name = "typing-extensions" },
@@ -235,7 +252,8 @@ name = "bs4"
version = "0.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "beautifulsoup4" },
{ name = "beautifulsoup4", version = "4.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "beautifulsoup4", version = "4.14.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698, upload-time = "2024-01-17T18:15:47.371Z" }
wheels = [
@@ -364,7 +382,7 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
wheels = [
@@ -381,7 +399,7 @@ resolution-markers = [
"python_full_version == '3.10.*'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [
@@ -563,7 +581,7 @@ name = "importlib-metadata"
version = "8.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp", marker = "python_full_version < '3.10'" },
{ name = "zipp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
wheels = [
@@ -591,7 +609,7 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "importlib-metadata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1e/82/fa43935523efdfcce6abbae9da7f372b627b27142c3419fcf13bf5b0c397/isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481", size = 824325, upload-time = "2025-10-01T16:26:45.027Z" }
wheels = [
@@ -790,6 +808,7 @@ dependencies = [
{ name = "requests" },
{ name = "tabulate" },
{ name = "tqdm" },
{ name = "tzdata" },
]
[package.dev-dependencies]
@@ -807,7 +826,8 @@ requires-dist = [
{ name = "pywidevine", specifier = ">=1.9.0" },
{ name = "requests", specifier = ">=2.32.5" },
{ name = "tabulate", specifier = ">=0.9.0" },
{ name = "tqdm", specifier = ">=4.68.2" },
{ name = "tqdm", specifier = ">=4.70.0" },
{ name = "tzdata", specifier = ">=2026.3" },
]
[package.metadata.requires-dev]
@@ -1054,15 +1074,15 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "astroid", version = "3.3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
{ name = "dill", marker = "python_full_version < '3.10'" },
{ name = "isort", version = "6.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "mccabe", marker = "python_full_version < '3.10'" },
{ name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "tomli", marker = "python_full_version < '3.10'" },
{ name = "tomlkit", marker = "python_full_version < '3.10'" },
{ name = "typing-extensions", marker = "python_full_version < '3.10'" },
{ name = "astroid", version = "3.3.11", source = { registry = "https://pypi.org/simple" } },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "dill" },
{ name = "isort", version = "6.1.0", source = { registry = "https://pypi.org/simple" } },
{ name = "mccabe" },
{ name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" } },
{ name = "tomli" },
{ name = "tomlkit" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/04/9d/81c84a312d1fa8133b0db0c76148542a98349298a01747ab122f9314b04e/pylint-3.3.9.tar.gz", hash = "sha256:d312737d7b25ccf6b01cc4ac629b5dcd14a0fcf3ec392735ac70f137a9d5f83a", size = 1525946, upload-time = "2025-10-05T18:41:43.786Z" }
wheels = [
@@ -1079,14 +1099,14 @@ resolution-markers = [
"python_full_version == '3.10.*'",
]
dependencies = [
{ name = "astroid", version = "4.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
{ name = "dill", marker = "python_full_version >= '3.10'" },
{ name = "isort", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "mccabe", marker = "python_full_version >= '3.10'" },
{ name = "platformdirs", version = "4.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "tomli", marker = "python_full_version == '3.10.*'" },
{ name = "tomlkit", marker = "python_full_version >= '3.10'" },
{ name = "astroid", version = "4.0.2", source = { registry = "https://pypi.org/simple" } },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "dill" },
{ name = "isort", version = "7.0.0", source = { registry = "https://pypi.org/simple" } },
{ name = "mccabe" },
{ name = "platformdirs", version = "4.5.0", source = { registry = "https://pypi.org/simple" } },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "tomlkit" },
]
sdist = { url = "https://files.pythonhosted.org/packages/dd/9c/0500020a5446031220f487ca0c762713c6f3ddad7231b811aaf1d473f6aa/pylint-4.0.3.tar.gz", hash = "sha256:a427fe76e0e5355e9fb9b604fd106c419cafb395886ba7f3cebebb03f30e081d", size = 1570368, upload-time = "2025-11-13T15:54:41.394Z" }
wheels = [
@@ -1223,11 +1243,11 @@ wheels = [
[[package]]
name = "soupsieve"
version = "2.8.4"
version = "2.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" }
sdist = { url = "https://files.pythonhosted.org/packages/80/f1/93422647dd7e461f23d254e6b2bfa687a85b53aeb4903fcdbb74474d4584/soupsieve-2.9.tar.gz", hash = "sha256:acee8417325c5653e1377dc31eccad59eb82cbc65942afe6174c53b3aaad63fc", size = 122122, upload-time = "2026-07-19T01:35:18.425Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" },
{ url = "https://files.pythonhosted.org/packages/7b/d6/3185ab5ad1280319b31986898f3206dd7227cd75e293d4dba2a5e6bf27a0/soupsieve-2.9-py3-none-any.whl", hash = "sha256:a2b2c76d67df2382d245409fd71e321a571717e58463efa32ace87dcadac2c12", size = 37387, upload-time = "2026-07-19T01:35:17.106Z" },
]
[[package]]
@@ -1299,14 +1319,14 @@ wheels = [
[[package]]
name = "tqdm"
version = "4.68.2"
version = "4.70.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/85/05/0d5260f1f1ca784f4a4a0def9cbe6affe587f5b4025328d446c3d67765f4/tqdm-4.68.2.tar.gz", hash = "sha256:89c230e8dbc67c7615c142487111222f878c77427ea09549960f62389e258add", size = 171923, upload-time = "2026-06-09T13:26:42.539Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz", hash = "sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4", size = 171846, upload-time = "2026-09-11T07:25:16.601Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/75/1a0392bcc21c44dcdf87b3cf2d137e7829be2c083a1e38d44efca3d57a16/tqdm-4.68.2-py3-none-any.whl", hash = "sha256:d4240441fb5353290b87d6a85968c9decc131a99b8c7faa28269d829de669ede", size = 78578, upload-time = "2026-06-09T13:26:40.731Z" },
{ url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199, upload-time = "2026-09-11T07:25:14.599Z" },
]
[[package]]
@@ -1318,6 +1338,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "tzdata"
version = "2026.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e4/31/3d74fa778a63b98b7374323befcc0be5ab3bd94afd4096a0124e7379152c/tzdata-2026.4.tar.gz", hash = "sha256:f1b8bd365d8d210c55353f4d7f8d6d8561c0ba50d704b700d195a9424bba0d79", size = 199350, upload-time = "2026-09-12T12:56:03.251Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl", hash = "sha256:c2169a8b0a7a5e9674da5a135ccdfb2b3e671b333ed9fed17b41f73c34476e81", size = 347494, upload-time = "2026-09-12T12:56:01.67Z" },
]
[[package]]
name = "unidecode"
version = "1.4.0"