ttyplayer 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ttyplayer/__init__.py +2 -0
- ttyplayer/cli.py +343 -0
- ttyplayer/control.py +244 -0
- ttyplayer/favorites.py +90 -0
- ttyplayer/history.py +64 -0
- ttyplayer/models.py +13 -0
- ttyplayer/player.py +652 -0
- ttyplayer/tui.py +618 -0
- ttyplayer/tui.tcss +125 -0
- ttyplayer/utils.py +80 -0
- ttyplayer/youtube.py +57 -0
- ttyplayer-0.3.0.dist-info/METADATA +222 -0
- ttyplayer-0.3.0.dist-info/RECORD +16 -0
- ttyplayer-0.3.0.dist-info/WHEEL +4 -0
- ttyplayer-0.3.0.dist-info/entry_points.txt +3 -0
- ttyplayer-0.3.0.dist-info/licenses/LICENSE +21 -0
ttyplayer/history.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""What has been played, one JSON object per line, newest at the bottom."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ttyplayer.models import Video
|
|
8
|
+
from ttyplayer.utils import data_path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def history_path() -> Path:
|
|
12
|
+
return data_path("history.jsonl")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def record(video: Video, path: Path | None = None):
|
|
16
|
+
path = path or history_path()
|
|
17
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
entry = {
|
|
19
|
+
"id": video.id,
|
|
20
|
+
"title": video.title,
|
|
21
|
+
"uploader": video.uploader,
|
|
22
|
+
"duration": video.duration,
|
|
23
|
+
"played_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
24
|
+
}
|
|
25
|
+
with path.open("a", encoding="utf-8") as f:
|
|
26
|
+
f.write(json.dumps(entry) + "\n")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def clear(path: Path | None = None) -> int:
|
|
30
|
+
"""Forget everything. Returns how many entries were there."""
|
|
31
|
+
path = path or history_path()
|
|
32
|
+
if not path.exists():
|
|
33
|
+
return 0
|
|
34
|
+
count = len(path.read_text(encoding="utf-8").splitlines())
|
|
35
|
+
path.unlink()
|
|
36
|
+
return count
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load(path: Path | None = None, limit: int = 20) -> list[Video]:
|
|
40
|
+
"""Most recently played first, each video once, at most `limit` of them."""
|
|
41
|
+
path = path or history_path()
|
|
42
|
+
if not path.exists():
|
|
43
|
+
return []
|
|
44
|
+
seen = set()
|
|
45
|
+
videos = []
|
|
46
|
+
for line in reversed(path.read_text(encoding="utf-8").splitlines()):
|
|
47
|
+
try:
|
|
48
|
+
entry = json.loads(line)
|
|
49
|
+
except json.JSONDecodeError:
|
|
50
|
+
continue
|
|
51
|
+
if entry.get("id") in seen or "id" not in entry:
|
|
52
|
+
continue
|
|
53
|
+
seen.add(entry["id"])
|
|
54
|
+
videos.append(
|
|
55
|
+
Video(
|
|
56
|
+
id=entry["id"],
|
|
57
|
+
title=entry.get("title") or "Untitled",
|
|
58
|
+
uploader=entry.get("uploader") or "Unknown",
|
|
59
|
+
duration=entry.get("duration"),
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
if len(videos) == limit:
|
|
63
|
+
break
|
|
64
|
+
return videos
|
ttyplayer/models.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Video:
|
|
6
|
+
id: str
|
|
7
|
+
title: str
|
|
8
|
+
uploader: str
|
|
9
|
+
duration: int | None # seconds; None for live streams or when yt-dlp does not know
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def url(self):
|
|
13
|
+
return f"https://www.youtube.com/watch?v={self.id}"
|