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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: showtime-cli
3
- Version: 0.3.26
3
+ Version: 0.3.27
4
4
  Summary: Command line show tracker using the TVMaze public API
5
5
  Author-email: Evgeniy Vasilev <aquilax@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "showtime-cli"
7
- version = "0.3.26"
7
+ version = "0.3.27"
8
8
  description = "Command line show tracker using the TVMaze public API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -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
- """Show tracked movies [movies <query>]"""
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 show(s) by id [follow <show_id>[,<show_id>...]]"""
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
- """Show all followed shows [shows <query>]"""
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
- """Show all episodes for a show [episodes <show_id>]"""
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
- """Remove show from the context [unset_show]"""
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
- """Show list of completed shows"""
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
- """Synchronize episodes with TVMaze [sync]"""
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
- """Synchronize episodes with TVMaze [sync]"""
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
- """Mark next unwatched episode as watched [watch_next <show_id>]"""
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 not watched [unwatch <episode_id>]"""
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 in a show as watched [watch_all <show_id>]"""
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 in a show as not watched [unwatch_all <show_id>]"""
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 show and season as watched [watch_all_season <show_id> <season>]"""
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 show and season as not watched [unwatch_all_season <show_id> <season>]"""
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
- """Show list of all episodes not watched yet [unwatched]"""
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 as seen up to the defined one [last_seen <show_id> <season> <episode>]"""
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 seen episodes between dates[export <from_date> <to_date>]"""
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
- """Export seen episodes between dates[watched_between <from_date> <to_date> <format>]"""
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 csv file"""
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, _: Statement) -> None:
415
- """Statistics about watchtime"""
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
- minutes = reduce(
418
- (lambda acc, ep: acc + ep['runtime'] if ep['runtime'] else 0), watched, 0)
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 month"""
422
- month = episode['watched'][0:7]
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 month in acc:
425
- acc[month] = {'episodes': 0, 'minutes': 0}
426
- acc[month]['episodes'] = acc[month]['episodes'] + 1
427
- acc[month]['minutes'] = acc[month]['minutes'] + runtime
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
- month_totals: Dict[str, Dict[str, int]] = reduce(month_grouper, watched, {})
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(month_totals)
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
- """Show list of unfinished shows"""
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='Tracked Movies').table)
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, month_totals: Dict[str, Dict[str, int]]) -> str:
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
- 'Month',
241
+ period.capitalize(),
242
+ 'Shows',
239
243
  'Episodes',
244
+ 'Movies',
240
245
  'Minutes',
241
246
  ])
242
- for date in sorted(month_totals.keys()):
247
+ for date in sorted(period_totals.keys()):
243
248
  data.append([
244
249
  date,
245
- str(month_totals[date]['episodes']),
246
- str(month_totals[date]['minutes']),
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 month'
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: showtime-cli
3
- Version: 0.3.26
3
+ Version: 0.3.27
4
4
  Summary: Command line show tracker using the TVMaze public API
5
5
  Author-email: Evgeniy Vasilev <aquilax@gmail.com>
6
6
  License: MIT
@@ -1,4 +0,0 @@
1
- """Common module information"""
2
-
3
- __version__ = '0.3.22'
4
- __url__ = 'https://github.com/aquilax/showtime'
File without changes
File without changes
File without changes