showtime-cli 0.3.26__tar.gz → 0.3.27__tar.gz
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.
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/PKG-INFO +1 -1
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/pyproject.toml +1 -1
- showtime_cli-0.3.27/showtime/__init__.py +12 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime/command.py +105 -44
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime/database.py +10 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime/output.py +19 -10
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime/showtime.py +8 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime_cli.egg-info/PKG-INFO +1 -1
- showtime_cli-0.3.26/showtime/__init__.py +0 -4
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/LICENSE +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/README.md +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/setup.cfg +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime/api.py +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime/config.py +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime/types.py +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime_cli.egg-info/SOURCES.txt +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime_cli.egg-info/dependency_links.txt +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime_cli.egg-info/entry_points.txt +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime_cli.egg-info/requires.txt +0 -0
- {showtime_cli-0.3.26 → showtime_cli-0.3.27}/showtime_cli.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Common module information"""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
4
|
+
|
|
5
|
+
__version__ = 'unknown'
|
|
6
|
+
__url__ = 'https://github.com/aquilax/showtime'
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = version("your_package_name")
|
|
10
|
+
except PackageNotFoundError:
|
|
11
|
+
pass
|
|
12
|
+
|
|
@@ -15,7 +15,7 @@ from showtime.config import Config
|
|
|
15
15
|
from showtime.database import get_cashed_write_db, get_memory_db
|
|
16
16
|
from showtime.output import Output
|
|
17
17
|
from showtime.showtime import ShowtimeApp
|
|
18
|
-
from showtime.types import Episode, EpisodeId, MovieId, Show, ShowId
|
|
18
|
+
from showtime.types import Episode, EpisodeId, Movie, MovieId, Show, ShowId
|
|
19
19
|
|
|
20
20
|
from . import __version__
|
|
21
21
|
|
|
@@ -86,7 +86,7 @@ class Showtime(Cmd):
|
|
|
86
86
|
|
|
87
87
|
@cmd2.with_category(SHOW_CATEGORY)
|
|
88
88
|
def do_search(self, statement: Statement) -> None:
|
|
89
|
-
"""Search shows [search <query>]"""
|
|
89
|
+
"""Search for shows by name [search <query>]"""
|
|
90
90
|
search_result = self.app.show_search_api(statement)
|
|
91
91
|
search_result_table = self.output.format_search_results(search_result)
|
|
92
92
|
self.output.poutput(search_result_table)
|
|
@@ -103,7 +103,7 @@ class Showtime(Cmd):
|
|
|
103
103
|
|
|
104
104
|
@cmd2.with_category(MOVIE_CATEGORY)
|
|
105
105
|
def do_movie_add(self, statement: Statement) -> None:
|
|
106
|
-
"""Add a movie from TMDB [movie_add <tmdb_id>]"""
|
|
106
|
+
"""Add a movie from TMDB to your tracked list [movie_add <tmdb_id>]"""
|
|
107
107
|
try:
|
|
108
108
|
movie = self.app.movie_add(MovieId(statement))
|
|
109
109
|
except RuntimeError as error:
|
|
@@ -113,7 +113,7 @@ class Showtime(Cmd):
|
|
|
113
113
|
|
|
114
114
|
@cmd2.with_category(MOVIE_CATEGORY)
|
|
115
115
|
def do_movie_add_watched(self, statement: Statement) -> None:
|
|
116
|
-
"""Add a movie and mark it watched [movie_add_watched <tmdb_id>]"""
|
|
116
|
+
"""Add a movie and mark it as watched [movie_add_watched <tmdb_id>]"""
|
|
117
117
|
try:
|
|
118
118
|
movie = self.app.movie_add_watched(MovieId(statement), self._get_current_datetime())
|
|
119
119
|
except RuntimeError as error:
|
|
@@ -134,10 +134,35 @@ class Showtime(Cmd):
|
|
|
134
134
|
|
|
135
135
|
@cmd2.with_category(MOVIE_CATEGORY)
|
|
136
136
|
def do_movies(self, query: Statement) -> None:
|
|
137
|
-
"""
|
|
137
|
+
"""List tracked movies matching a query [movies <query>]"""
|
|
138
138
|
movies = self.app.movie_search(query)
|
|
139
139
|
self.output.ppaged(self.output.movies_table(movies))
|
|
140
140
|
|
|
141
|
+
@cmd2.with_category(MOVIE_CATEGORY)
|
|
142
|
+
def do_movie_watched_between(self, statement: Statement) -> None:
|
|
143
|
+
"""Show movies watched between two dates [movie_watched_between <from_date> <to_date> [format]]"""
|
|
144
|
+
try:
|
|
145
|
+
output_format = 'table'
|
|
146
|
+
split_arg = statement.split(' ')
|
|
147
|
+
if len(split_arg) < 3:
|
|
148
|
+
from_date_s, to_date_s = split_arg
|
|
149
|
+
else:
|
|
150
|
+
from_date_s, to_date_s, output_format = split_arg
|
|
151
|
+
from_date = dateutil.parser.parse(from_date_s).date()
|
|
152
|
+
to_date = dateutil.parser.parse(to_date_s).date()
|
|
153
|
+
|
|
154
|
+
except (dateutil.parser.ParserError, OverflowError):
|
|
155
|
+
self.output.perror("Invalid date")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
movies = self.app.movies_watched_between(from_date, to_date)
|
|
159
|
+
sorted_movies = sorted(movies, key=lambda movie: movie['watched'])
|
|
160
|
+
if output_format == 'json':
|
|
161
|
+
movies_output = self.output.movies_json(sorted_movies)
|
|
162
|
+
else:
|
|
163
|
+
movies_output = self.output.movies_table(sorted_movies, title='Watched movies')
|
|
164
|
+
self.output.ppaged(movies_output)
|
|
165
|
+
|
|
141
166
|
def _set_movie_watched(self, statement: Statement, watched: bool) -> None:
|
|
142
167
|
movie_id = MovieId(statement)
|
|
143
168
|
movie = self.app.movie_get(movie_id)
|
|
@@ -170,7 +195,7 @@ class Showtime(Cmd):
|
|
|
170
195
|
|
|
171
196
|
@cmd2.with_category(SHOW_CATEGORY)
|
|
172
197
|
def do_follow(self, statement: Statement) -> None:
|
|
173
|
-
"""Follow
|
|
198
|
+
"""Follow one or more shows by ID [follow <show_id>[,<show_id>...]]"""
|
|
174
199
|
show_ids = self._get_list(statement)
|
|
175
200
|
|
|
176
201
|
for show_id in show_ids:
|
|
@@ -181,7 +206,7 @@ class Showtime(Cmd):
|
|
|
181
206
|
|
|
182
207
|
@cmd2.with_category(SHOW_CATEGORY)
|
|
183
208
|
def do_shows(self, query: Statement) -> None:
|
|
184
|
-
"""
|
|
209
|
+
"""List followed shows matching a query [shows <query>]"""
|
|
185
210
|
shows = self.app.show_search(query)
|
|
186
211
|
self._show_ids = [ShowId(s['id']) for s in shows]
|
|
187
212
|
shows_table = self.output.shows_table(shows)
|
|
@@ -189,7 +214,7 @@ class Showtime(Cmd):
|
|
|
189
214
|
|
|
190
215
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
191
216
|
def do_episodes(self, query: Statement) -> None:
|
|
192
|
-
"""
|
|
217
|
+
"""List episodes for a show [episodes <show_id>]"""
|
|
193
218
|
show_id = self._get_show_id(query)
|
|
194
219
|
show = self.app.show_get(show_id)
|
|
195
220
|
if not show:
|
|
@@ -202,7 +227,7 @@ class Showtime(Cmd):
|
|
|
202
227
|
|
|
203
228
|
@cmd2.with_category(SHOW_CATEGORY)
|
|
204
229
|
def do_set_show(self, statement: Statement) -> None:
|
|
205
|
-
"""Set show in context [set_show <show_id>]"""
|
|
230
|
+
"""Set the active show in context [set_show <show_id>]"""
|
|
206
231
|
show_id = self._get_show_id(statement)
|
|
207
232
|
show = self.app.show_get(show_id)
|
|
208
233
|
if not show:
|
|
@@ -213,20 +238,20 @@ class Showtime(Cmd):
|
|
|
213
238
|
|
|
214
239
|
@cmd2.with_category(SHOW_CATEGORY)
|
|
215
240
|
def do_unset_show(self, _: Statement) -> None:
|
|
216
|
-
"""
|
|
241
|
+
"""Clear the active show from context [unset_show]"""
|
|
217
242
|
self.current_show = None
|
|
218
243
|
self.prompt = self._get_prompt()
|
|
219
244
|
|
|
220
245
|
@cmd2.with_category(SHOW_CATEGORY)
|
|
221
246
|
def do_completed(self, _: Statement) -> None:
|
|
222
|
-
"""
|
|
247
|
+
"""List completed shows"""
|
|
223
248
|
shows = self.app.show_get_completed()
|
|
224
249
|
completed_shows_table = self.output.completed_shows_table(shows)
|
|
225
250
|
self.output.ppaged(completed_shows_table)
|
|
226
251
|
|
|
227
252
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
228
253
|
def do_sync(self, _: Statement) -> None:
|
|
229
|
-
"""
|
|
254
|
+
"""Refresh episodes for active shows from TVMaze [sync]"""
|
|
230
255
|
self.output.pfeedback('Syncing shows...')
|
|
231
256
|
self.app.sync(on_show_sync=self.output.status_on_show_sync,
|
|
232
257
|
on_episode_insert=self.output.status_on_episode_insert,
|
|
@@ -236,7 +261,7 @@ class Showtime(Cmd):
|
|
|
236
261
|
|
|
237
262
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
238
263
|
def do_sync_all(self, _: Statement) -> None:
|
|
239
|
-
"""
|
|
264
|
+
"""Refresh all tracked shows from TVMaze [sync_all]"""
|
|
240
265
|
self.output.pfeedback('Syncing shows...')
|
|
241
266
|
self.app.sync(on_show_sync=self.output.status_on_show_sync,
|
|
242
267
|
on_episode_insert=self.output.status_on_episode_insert,
|
|
@@ -246,19 +271,19 @@ class Showtime(Cmd):
|
|
|
246
271
|
|
|
247
272
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
248
273
|
def do_watch(self, statement: Statement) -> None:
|
|
249
|
-
"""Mark episodes as watched [watch <episode_id,...>]"""
|
|
274
|
+
"""Mark one or more episodes as watched [watch <episode_id,...>]"""
|
|
250
275
|
when = self._get_current_datetime()
|
|
251
276
|
for episode_id in [e.strip() for e in statement.split(',')]:
|
|
252
277
|
self.app.episode_update_watched(EpisodeId(episode_id), when)
|
|
253
278
|
|
|
254
279
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
255
280
|
def do_next(self, statement: Statement) -> None:
|
|
256
|
-
"""Mark next unwatched episode as watched [next <show_id>]"""
|
|
281
|
+
"""Mark the next unwatched episode as watched [next <show_id>]"""
|
|
257
282
|
self.do_watch_next(statement)
|
|
258
283
|
|
|
259
284
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
260
285
|
def do_watch_next(self, statement: Statement) -> None:
|
|
261
|
-
"""
|
|
286
|
+
"""Confirm and mark the next unwatched episode as watched [watch_next <show_id>]"""
|
|
262
287
|
show_id = self._get_show_id(statement)
|
|
263
288
|
show = self.app.show_get(show_id)
|
|
264
289
|
if not show:
|
|
@@ -279,7 +304,7 @@ class Showtime(Cmd):
|
|
|
279
304
|
|
|
280
305
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
281
306
|
def do_delete_episode(self, statement: Statement) -> None:
|
|
282
|
-
"""Delete episode [delete_episode <episode_id>]"""
|
|
307
|
+
"""Delete an episode after confirmation [delete_episode <episode_id>]"""
|
|
283
308
|
episode_id = EpisodeId(statement)
|
|
284
309
|
episode = self.app.episode_get(episode_id)
|
|
285
310
|
if episode:
|
|
@@ -295,41 +320,41 @@ class Showtime(Cmd):
|
|
|
295
320
|
|
|
296
321
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
297
322
|
def do_unwatch(self, statement: Statement) -> None:
|
|
298
|
-
"""Mark episode as
|
|
323
|
+
"""Mark an episode as unwatched [unwatch <episode_id>]"""
|
|
299
324
|
when = self._get_current_datetime()
|
|
300
325
|
self.app.episode_update_not_watched(EpisodeId(statement), when)
|
|
301
326
|
|
|
302
327
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
303
328
|
def do_watch_all(self, statement: Statement) -> None:
|
|
304
|
-
"""Mark all episodes
|
|
329
|
+
"""Mark all episodes for a show as watched [watch_all <show_id>]"""
|
|
305
330
|
show_id = self._get_show_id(statement)
|
|
306
331
|
when = self._get_current_datetime()
|
|
307
332
|
self.app.episodes_update_all_watched(show_id, when)
|
|
308
333
|
|
|
309
334
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
310
335
|
def do_unwatch_all(self, statement: Statement) -> None:
|
|
311
|
-
"""Mark all episodes
|
|
336
|
+
"""Mark all episodes for a show as unwatched [unwatch_all <show_id>]"""
|
|
312
337
|
show_id = self._get_show_id(statement)
|
|
313
338
|
when = self._get_current_datetime()
|
|
314
339
|
self.app.episodes_update_all_not_watched(show_id, when)
|
|
315
340
|
|
|
316
341
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
317
342
|
def do_watch_all_season(self, statement: Statement) -> None:
|
|
318
|
-
"""Mark all episodes in a
|
|
343
|
+
"""Mark all episodes in a season as watched [watch_all_season <show_id> <season>]"""
|
|
319
344
|
show_id, season = statement.split(' ')
|
|
320
345
|
when = self._get_current_datetime()
|
|
321
346
|
self.app.episodes_update_season_watched(ShowId(show_id), int(season), when)
|
|
322
347
|
|
|
323
348
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
324
349
|
def do_unwatch_all_season(self, statement: Statement) -> None:
|
|
325
|
-
"""Mark all episodes in a
|
|
350
|
+
"""Mark all episodes in a season as unwatched [unwatch_all_season <show_id> <season>]"""
|
|
326
351
|
show_id, season = statement.split(' ')
|
|
327
352
|
when = self._get_current_datetime()
|
|
328
353
|
self.app.episodes_update_season_not_watched(ShowId(show_id), int(season), when)
|
|
329
354
|
|
|
330
355
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
331
356
|
def do_unwatched(self, _: Statement) -> None:
|
|
332
|
-
"""
|
|
357
|
+
"""List all unwatched episodes [unwatched]"""
|
|
333
358
|
when = self._get_current_datetime()
|
|
334
359
|
episodes = self.app.episodes_get_unwatched(when)
|
|
335
360
|
episodes_table = self.output.format_unwatched(episodes)
|
|
@@ -337,20 +362,20 @@ class Showtime(Cmd):
|
|
|
337
362
|
|
|
338
363
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
339
364
|
def do_last_seen(self, statement: Statement) -> None:
|
|
340
|
-
"""Mark all episodes
|
|
365
|
+
"""Mark all episodes up to a specific episode as watched [last_seen <show_id> <season> <episode>]"""
|
|
341
366
|
show_id, season, episode = statement.split(' ')
|
|
342
367
|
when = self._get_current_datetime()
|
|
343
368
|
count = self.app.episodes_watched_to_last_seen(ShowId(show_id), int(season), int(episode), when)
|
|
344
369
|
self.output.poutput(f'{count} episodes marked as seen')
|
|
345
370
|
|
|
346
371
|
def do_config(self, _: Statement) -> None:
|
|
347
|
-
"""Show current configuration [config]"""
|
|
372
|
+
"""Show the current configuration [config]"""
|
|
348
373
|
config = self.app.config_get()
|
|
349
374
|
path = config.get('Database', 'Path')
|
|
350
375
|
self.output.poutput(f'Database path: {path}')
|
|
351
376
|
|
|
352
377
|
def do_export(self, statement: Statement) -> None:
|
|
353
|
-
"""Export
|
|
378
|
+
"""Export watched episodes between two dates [export <from_date> <to_date>]"""
|
|
354
379
|
try:
|
|
355
380
|
from_date_s, to_date_s = statement.split(' ')
|
|
356
381
|
from_date = dateutil.parser.parse(from_date_s).date()
|
|
@@ -365,7 +390,7 @@ class Showtime(Cmd):
|
|
|
365
390
|
|
|
366
391
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
367
392
|
def do_watched_between(self, statement: Statement) -> None:
|
|
368
|
-
"""
|
|
393
|
+
"""Show watched episodes between two dates [watched_between <from_date> <to_date> [format]]"""
|
|
369
394
|
try:
|
|
370
395
|
output_format = 'table'
|
|
371
396
|
split_arg = statement.split(' ')
|
|
@@ -385,12 +410,12 @@ class Showtime(Cmd):
|
|
|
385
410
|
if output_format == 'json':
|
|
386
411
|
episodes_output = self.output.episodes_json(sorted_episodes)
|
|
387
412
|
else:
|
|
388
|
-
episodes_output = self.output.format_unwatched(sorted_episodes)
|
|
413
|
+
episodes_output = self.output.format_unwatched(sorted_episodes, title='Watched episodes')
|
|
389
414
|
self.output.ppaged(episodes_output)
|
|
390
415
|
|
|
391
416
|
@cmd2.with_category(EPISODE_CATEGORY)
|
|
392
417
|
def do_new_unwatched(self, statement: Statement) -> None:
|
|
393
|
-
"""Show unwatched episodes aired in the last 7 days[new_unwatched <days>]"""
|
|
418
|
+
"""Show unwatched episodes aired in the last 7 days by default [new_unwatched <days>]"""
|
|
394
419
|
spl_statement = statement.split(' ')
|
|
395
420
|
days = int(spl_statement[0]) if len(spl_statement) > 0 and spl_statement[0] != '' else 7
|
|
396
421
|
d_start_date = dateutil.parser.parse(spl_statement[1]).date() if len(spl_statement) > 1 else date.today()
|
|
@@ -404,38 +429,74 @@ class Showtime(Cmd):
|
|
|
404
429
|
self.output.ppaged(episodes_table)
|
|
405
430
|
|
|
406
431
|
def do_version(self, _: Statement) -> None:
|
|
407
|
-
"""Show current version"""
|
|
432
|
+
"""Show the current version number"""
|
|
408
433
|
self.output.poutput(__version__)
|
|
409
434
|
|
|
410
435
|
def do_patch_watchtime(self, file_name: Statement) -> None:
|
|
411
|
-
"""Update watch times from
|
|
436
|
+
"""Update episode watch times from a CSV file [patch_watchtime <file>]"""
|
|
412
437
|
self.app.episodes_patch_watchtime(file_name)
|
|
413
438
|
|
|
414
|
-
def do_watching_stats(self,
|
|
415
|
-
"""
|
|
439
|
+
def do_watching_stats(self, statement: Statement) -> None:
|
|
440
|
+
"""Show watch-time statistics for a period [watching_stats <day|week|month|year>]"""
|
|
441
|
+
period = str(statement).strip().lower() or 'month'
|
|
442
|
+
if period not in {'day', 'week', 'month', 'year'}:
|
|
443
|
+
self.output.perror('Invalid period. Choose from: day, week, month, year')
|
|
444
|
+
return
|
|
445
|
+
|
|
416
446
|
watched = self.app.episodes_get_watched()
|
|
417
|
-
|
|
418
|
-
|
|
447
|
+
watched_movies = self.app.movies_get_watched()
|
|
448
|
+
episode_minutes = sum(ep['runtime'] or 0 for ep in watched)
|
|
449
|
+
movie_minutes = sum(movie['runtime'] or 0 for movie in watched_movies)
|
|
450
|
+
minutes = episode_minutes + movie_minutes
|
|
451
|
+
|
|
452
|
+
def period_key(watched_at: str) -> str:
|
|
453
|
+
watched_date = dateutil.parser.parse(watched_at).date()
|
|
454
|
+
if period == 'day':
|
|
455
|
+
return watched_date.isoformat()
|
|
456
|
+
if period == 'week':
|
|
457
|
+
iso_year, iso_week, _ = watched_date.isocalendar()
|
|
458
|
+
return f'{iso_year}-W{iso_week:02}'
|
|
459
|
+
if period == 'year':
|
|
460
|
+
return str(watched_date.year)
|
|
461
|
+
return watched_date.strftime('%Y-%m')
|
|
462
|
+
|
|
463
|
+
shows_by_period: Dict[str, set[int]] = {}
|
|
419
464
|
|
|
420
465
|
def month_grouper(acc: Dict[str, Dict[str, int]], episode: Episode) -> Dict[str, Dict[str, int]]:
|
|
421
|
-
"""Groups watched episodes by
|
|
422
|
-
|
|
466
|
+
"""Groups watched episodes by the selected period"""
|
|
467
|
+
group = period_key(episode['watched'])
|
|
423
468
|
runtime = episode['runtime'] if episode['runtime'] else 0
|
|
424
|
-
if not
|
|
425
|
-
acc[
|
|
426
|
-
|
|
427
|
-
|
|
469
|
+
if group not in acc:
|
|
470
|
+
acc[group] = {'shows': 0, 'episodes': 0, 'movies': 0, 'minutes': 0}
|
|
471
|
+
watched_shows = shows_by_period.setdefault(group, set())
|
|
472
|
+
if episode['show_id'] not in watched_shows:
|
|
473
|
+
watched_shows.add(episode['show_id'])
|
|
474
|
+
acc[group]['shows'] = acc[group]['shows'] + 1
|
|
475
|
+
acc[group]['episodes'] = acc[group]['episodes'] + 1
|
|
476
|
+
acc[group]['minutes'] = acc[group]['minutes'] + runtime
|
|
477
|
+
return acc
|
|
478
|
+
|
|
479
|
+
def movie_month_grouper(acc: Dict[str, Dict[str, int]], movie: Movie) -> Dict[str, Dict[str, int]]:
|
|
480
|
+
"""Groups watched movies by the selected period"""
|
|
481
|
+
group = period_key(movie['watched'])
|
|
482
|
+
runtime = movie['runtime'] if movie['runtime'] else 0
|
|
483
|
+
if group not in acc:
|
|
484
|
+
acc[group] = {'shows': 0, 'episodes': 0, 'movies': 0, 'minutes': 0}
|
|
485
|
+
acc[group]['movies'] = acc[group]['movies'] + 1
|
|
486
|
+
acc[group]['minutes'] = acc[group]['minutes'] + runtime
|
|
428
487
|
return acc
|
|
429
488
|
|
|
430
|
-
|
|
489
|
+
period_totals: Dict[str, Dict[str, int]] = reduce(month_grouper, watched, {})
|
|
490
|
+
period_totals = reduce(movie_month_grouper, watched_movies, period_totals)
|
|
431
491
|
self.output.poutput(f"Total watched episodes: {len(watched)}")
|
|
492
|
+
self.output.poutput(f"Total watched movies: {len(watched_movies)}")
|
|
432
493
|
self.output.poutput(f"Total watchtime in minutes: {minutes}")
|
|
433
|
-
summary_table = self.output.summary_table(
|
|
494
|
+
summary_table = self.output.summary_table(period_totals, period)
|
|
434
495
|
self.output.ppaged(summary_table)
|
|
435
496
|
|
|
436
497
|
@cmd2.with_category(SHOW_CATEGORY)
|
|
437
498
|
def do_unfinished(self, _: Statement) -> None:
|
|
438
|
-
"""
|
|
499
|
+
"""List shows that are still in progress"""
|
|
439
500
|
unfinished_shows = self.app.show_get_unfinished()
|
|
440
501
|
unfinished_shows_table = self.output.unfinished_shows_table(unfinished_shows)
|
|
441
502
|
self.output.ppaged(unfinished_shows_table)
|
|
@@ -94,6 +94,16 @@ class Database(TinyDB):
|
|
|
94
94
|
"""Returns movies sorted by title"""
|
|
95
95
|
return cast(List[Movie], sorted(self.table(MOVIE).all(), key=lambda movie: movie['title'].lower()))
|
|
96
96
|
|
|
97
|
+
def get_watched_movies(self) -> List[Movie]:
|
|
98
|
+
"""Returns all watched movies"""
|
|
99
|
+
return cast(List[Movie], self.table(MOVIE).search(where('watched') != NOT_WATCHED_VALUE))
|
|
100
|
+
|
|
101
|
+
def watched_movies_between(self, from_date: date, to_date: date) -> List[Movie]:
|
|
102
|
+
"""Returns movies watched between two dates"""
|
|
103
|
+
def is_between(in_date):
|
|
104
|
+
return _test_between(in_date, from_date, to_date)
|
|
105
|
+
return cast(List[Movie], self.table(MOVIE).search(where('watched').test(is_between)))
|
|
106
|
+
|
|
97
107
|
def get_movie(self, movie_id: MovieId) -> Optional[Movie]:
|
|
98
108
|
"""Returns a single movie"""
|
|
99
109
|
return cast(Optional[Movie], self.table(MOVIE).get(where('id') == movie_id))
|
|
@@ -92,7 +92,7 @@ class Output():
|
|
|
92
92
|
])
|
|
93
93
|
return str(Table(data, title='Movie Search Results').table)
|
|
94
94
|
|
|
95
|
-
def movies_table(self, movies: List[Movie]) -> str:
|
|
95
|
+
def movies_table(self, movies: List[Movie], title: str = 'Tracked Movies') -> str:
|
|
96
96
|
"""Formats tracked movies as a table"""
|
|
97
97
|
data = [['ID', 'Title', 'Released', 'Language', 'Runtime', 'Watched']]
|
|
98
98
|
for movie in movies:
|
|
@@ -104,7 +104,11 @@ class Output():
|
|
|
104
104
|
str(movie['runtime']) if movie['runtime'] is not None else '',
|
|
105
105
|
movie['watched'],
|
|
106
106
|
])
|
|
107
|
-
return str(Table(data, title=
|
|
107
|
+
return str(Table(data, title=title).table)
|
|
108
|
+
|
|
109
|
+
def movies_json(self, movies: List[Movie]) -> str:
|
|
110
|
+
"""Formats movies as json"""
|
|
111
|
+
return json.dumps(movies, sort_keys=True, indent=4)
|
|
108
112
|
|
|
109
113
|
def format_episodes(self, show: Show, episodes: List[Episode]) -> str:
|
|
110
114
|
"""Formats as table list of episodes"""
|
|
@@ -136,7 +140,7 @@ class Output():
|
|
|
136
140
|
])
|
|
137
141
|
return data
|
|
138
142
|
|
|
139
|
-
def format_unwatched(self, episodes: List[DecoratedEpisode]) -> str:
|
|
143
|
+
def format_unwatched(self, episodes: List[DecoratedEpisode], title: str = 'Episodes to watch') -> str:
|
|
140
144
|
"""Formats unwatched episodes as table"""
|
|
141
145
|
data = []
|
|
142
146
|
data.append([
|
|
@@ -158,7 +162,6 @@ class Output():
|
|
|
158
162
|
episode['airdate'],
|
|
159
163
|
episode['watched']
|
|
160
164
|
])
|
|
161
|
-
title = 'Episodes to watch'
|
|
162
165
|
return str(Table(data, title=title).table)
|
|
163
166
|
|
|
164
167
|
def shows_table(self, shows: List[Show]) -> str:
|
|
@@ -231,24 +234,30 @@ class Output():
|
|
|
231
234
|
table.justify_columns[1] = 'right'
|
|
232
235
|
return str(table.table)
|
|
233
236
|
|
|
234
|
-
def summary_table(self,
|
|
237
|
+
def summary_table(self, period_totals: Dict[str, Dict[str, int]], period: str = 'month') -> str:
|
|
235
238
|
"""Formats summary as a table"""
|
|
236
239
|
data = []
|
|
237
240
|
data.append([
|
|
238
|
-
|
|
241
|
+
period.capitalize(),
|
|
242
|
+
'Shows',
|
|
239
243
|
'Episodes',
|
|
244
|
+
'Movies',
|
|
240
245
|
'Minutes',
|
|
241
246
|
])
|
|
242
|
-
for date in sorted(
|
|
247
|
+
for date in sorted(period_totals.keys()):
|
|
243
248
|
data.append([
|
|
244
249
|
date,
|
|
245
|
-
str(
|
|
246
|
-
str(
|
|
250
|
+
str(period_totals[date]['shows']),
|
|
251
|
+
str(period_totals[date]['episodes']),
|
|
252
|
+
str(period_totals[date]['movies']),
|
|
253
|
+
str(period_totals[date]['minutes']),
|
|
247
254
|
])
|
|
248
|
-
title = 'Watchtime per
|
|
255
|
+
title = f'Watchtime per {period}'
|
|
249
256
|
table = Table(data, title=title)
|
|
250
257
|
table.justify_columns[1] = 'right'
|
|
251
258
|
table.justify_columns[2] = 'right'
|
|
259
|
+
table.justify_columns[3] = 'right'
|
|
260
|
+
table.justify_columns[4] = 'right'
|
|
252
261
|
return str(table.table)
|
|
253
262
|
|
|
254
263
|
def episodes_json(self, episodes: List[DecoratedEpisode]) -> str:
|
|
@@ -166,6 +166,14 @@ class ShowtimeApp():
|
|
|
166
166
|
"""Returns a tracked movie"""
|
|
167
167
|
return self.database.get_movie(movie_id)
|
|
168
168
|
|
|
169
|
+
def movies_get_watched(self) -> List[Movie]:
|
|
170
|
+
"""Returns all watched movies"""
|
|
171
|
+
return self.database.get_watched_movies()
|
|
172
|
+
|
|
173
|
+
def movies_watched_between(self, from_date: date, to_date: date) -> List[Movie]:
|
|
174
|
+
"""Returns watched movies between two dates"""
|
|
175
|
+
return self.database.watched_movies_between(from_date, to_date)
|
|
176
|
+
|
|
169
177
|
def movie_remove(self, movie_id: MovieId) -> List[int]:
|
|
170
178
|
"""Removes a tracked movie"""
|
|
171
179
|
with transaction(self.database) as transacted_db:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|