ASMRManager 2.2.3__tar.gz → 2.2.5__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.
Files changed (70) hide show
  1. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/PKG-INFO +1 -1
  2. asmrmanager-2.2.5/asmrmanager/_version.py +1 -0
  3. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/core.py +21 -6
  4. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/dl.py +15 -3
  5. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/file.py +19 -17
  6. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/fileconverter.py +10 -4
  7. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/config.py +1 -0
  8. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/manager.py +15 -11
  9. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/lrcplayer/main.py +2 -0
  10. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/spider/interface.py +7 -1
  11. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/pyproject.toml +1 -1
  12. asmrmanager-2.2.3/asmrmanager/_version.py +0 -1
  13. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/LICENSE +0 -0
  14. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/README.md +0 -0
  15. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/__init__.py +0 -0
  16. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/__main__.py +0 -0
  17. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/hold.py +0 -0
  18. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/info.py +0 -0
  19. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/main.py +0 -0
  20. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/pl.py +0 -0
  21. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/play.py +0 -0
  22. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/query.py +0 -0
  23. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/review.py +0 -0
  24. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/sql.py +0 -0
  25. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/utils.py +0 -0
  26. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/view.py +0 -0
  27. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/cli/which.py +0 -0
  28. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/__init__.py +0 -0
  29. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/browse_params.py +0 -0
  30. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/download_params.py +0 -0
  31. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/output.py +0 -0
  32. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/parse_filter.py +0 -0
  33. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/rj_parse.py +0 -0
  34. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/select.py +0 -0
  35. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/types.py +0 -0
  36. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/common/vtt2lrc.py +0 -0
  37. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/__init__.py +0 -0
  38. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/database.py +0 -0
  39. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/engine.py +0 -0
  40. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/manage.py +0 -0
  41. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/orm_type.py +0 -0
  42. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/q_func.py +0 -0
  43. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/utils/__init__.py +0 -0
  44. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/utils/add2db.py +0 -0
  45. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/database/utils/uuid_sqlite.py +0 -0
  46. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/__init__.py +0 -0
  47. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/appdirs_.py +0 -0
  48. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/exceptions.py +0 -0
  49. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/file_zipper.py +0 -0
  50. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/resources/config.example.toml +0 -0
  51. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/resources/sqls.example/circle_name.sql +0 -0
  52. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/resources/sqls.example/score.sql +0 -0
  53. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/resources/sqls.example/search.sql +0 -0
  54. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/resources/sqls.example/tags.sql +0 -0
  55. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/resources/sqls.example/test.sql +0 -0
  56. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/resources/sqls.example/vas.sql +0 -0
  57. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/filemanager/utils.py +0 -0
  58. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/logger/__init__.py +0 -0
  59. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/lrcplayer/__init__.py +0 -0
  60. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/lrcplayer/lrcparse.py +0 -0
  61. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/lrcplayer/main.css +0 -0
  62. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/lrcplayer/player/base.py +0 -0
  63. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/lrcplayer/player/mpdplayer.py +0 -0
  64. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/lrcplayer/player/pygameplayer.py +0 -0
  65. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/spider/__init__.py +0 -0
  66. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/spider/asmrapi.py +0 -0
  67. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/spider/downloader.py +0 -0
  68. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/spider/playlist.py +0 -0
  69. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/spider/utils/IDMHelper.py +0 -0
  70. {asmrmanager-2.2.3 → asmrmanager-2.2.5}/asmrmanager/spider/utils/aria2_downloader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ASMRManager
3
- Version: 2.2.3
3
+ Version: 2.2.5
4
4
  Summary: download, manage and play the voices on asmr.one
5
5
  Keywords: asmr,downloader,music,player,manager,cli,tui,commandline,terminal
6
6
  Author-Email: SLQY <sqiyel@gmail.com>
@@ -0,0 +1 @@
1
+ __version__ = "2.2.5"
@@ -394,7 +394,7 @@ SEPARATOR = ":"
394
394
  def interval_preprocess_cb(ctx: click.Context, opt: click.Parameter, val: str):
395
395
  """
396
396
  input must be numeric or None,
397
- and it returns a float/int/None value tuple
397
+ and it returns a float/int/str/None value tuple
398
398
  """
399
399
  if val is None:
400
400
  return (None, None)
@@ -404,12 +404,14 @@ def interval_preprocess_cb(ctx: click.Context, opt: click.Parameter, val: str):
404
404
  def _check(x: str):
405
405
  if x == "":
406
406
  return None
407
- try:
408
- xf: float = float(x)
409
- except ValueError:
410
- ctx.fail(f"{x} is not a valid number")
411
407
 
412
- assert isinstance(xf, float)
408
+ if not x.isdigit():
409
+ if x[-1] in "hm" and x[:-1].isdigit():
410
+ return x
411
+ logger.warning(f"{x} is not a valid number or time")
412
+ return None
413
+
414
+ xf: float = float(x)
413
415
  if xf.is_integer():
414
416
  return int(xf)
415
417
  return xf
@@ -417,6 +419,19 @@ def interval_preprocess_cb(ctx: click.Context, opt: click.Parameter, val: str):
417
419
  return tuple(map(_check, vals))
418
420
 
419
421
 
422
+ def time_interval_preprocess_cb(
423
+ ctx: click.Context, opt: click.Parameter, val: str
424
+ ):
425
+ return tuple(
426
+ map(
427
+ lambda x: str(x) + "m"
428
+ if (x is not None) and (not isinstance(x, str))
429
+ else x,
430
+ interval_preprocess_cb(ctx, opt, val),
431
+ )
432
+ )
433
+
434
+
420
435
  def pl_preprocess_cb(
421
436
  ctx: click.Context, param: click.Option, val: str | Tuple
422
437
  ) -> List[uuid.UUID]:
@@ -9,6 +9,7 @@ from asmrmanager.cli.core import (
9
9
  download_param_options,
10
10
  fm,
11
11
  interval_preprocess_cb,
12
+ time_interval_preprocess_cb,
12
13
  multi_rj_argument,
13
14
  )
14
15
  from asmrmanager.common.browse_params import BrowseParams
@@ -155,6 +156,12 @@ def check(source_ids: List[LocalSourceID]):
155
156
  @click.option(
156
157
  "--price", "-pr", help="pirce interval", callback=interval_preprocess_cb
157
158
  )
159
+ @click.option(
160
+ "--duration",
161
+ "-d",
162
+ help="duration interval",
163
+ callback=time_interval_preprocess_cb,
164
+ )
158
165
  @click.option(
159
166
  "all_",
160
167
  "--all/--select",
@@ -179,6 +186,7 @@ def search(
179
186
  rate: Tuple[float | None, float | None],
180
187
  sell: Tuple[int | None, int | None],
181
188
  price: Tuple[int | None, int | None],
189
+ duration: Tuple[str | None, str | None],
182
190
  browse_params: BrowseParams,
183
191
  download_params: DownloadParams,
184
192
  all_: bool,
@@ -194,12 +202,15 @@ def search(
194
202
 
195
203
  --tags tag1 --tags tag2 --no-tags tag3
196
204
 
197
- for options like --rate, --sell, --price, you should give a interval like:
205
+ for options like --rate, --sell, --price, --duration you should give a interval like:
198
206
 
199
- --rate 3.9:4.7 --sell 1000: --price :200
207
+ --rate 3.9:4.7 --sell 1000: --price :200 --duration 10:60
200
208
 
201
209
  the interval a:b means a <= x < b, if a or b is not given
202
210
  i.e. a: or :b, it means no lower or upper limit
211
+
212
+ for --duration, expressions like `1.5h(1.5 hours)`, `10m(10 minutes)` are allowed,
213
+ or by default, the unit is minute.
203
214
  """
204
215
  spider, db = create_downloader_and_database(
205
216
  download_params=download_params
@@ -209,7 +220,7 @@ def search(
209
220
  " ".join(
210
221
  map(
211
222
  lambda t: (t := t.strip())
212
- and ('-' + t[1:] if t.startswith("!") else t),
223
+ and ("-" + t[1:] if t.startswith("!") else t),
213
224
  keywords,
214
225
  )
215
226
  ),
@@ -224,6 +235,7 @@ def search(
224
235
  rate=rate,
225
236
  sell=sell,
226
237
  price=price,
238
+ duration=duration,
227
239
  params=browse_params,
228
240
  all_=all_,
229
241
  )
@@ -40,9 +40,9 @@ def del_(source_id: LocalSourceID):
40
40
 
41
41
  folders = folder_chooser_multiple(
42
42
  rj_path,
43
- lambda p: any([
44
- i.suffix != ".info" for i in p.iterdir() if not i.is_dir()
45
- ]),
43
+ lambda p: any(
44
+ [i.suffix != ".info" for i in p.iterdir() if not i.is_dir()]
45
+ ),
46
46
  )
47
47
 
48
48
  for folder in folders:
@@ -157,7 +157,9 @@ def store(source_ids: List[LocalSourceID], replace: bool, all_: bool):
157
157
  assert file.with_suffix(f".{to}").exists()
158
158
  file.unlink()
159
159
 
160
- def convert_all(from_: str, to: Literal["mp3", "flac", "m4a", "wav", "lrc"]):
160
+ def convert_all(
161
+ from_: str, to: Literal["mp3", "flac", "m4a", "wav", "lrc"]
162
+ ):
161
163
  for file in path.rglob(f"*.{from_}", case_sensitive=False):
162
164
  convert(file, to)
163
165
 
@@ -166,10 +168,10 @@ def store(source_ids: List[LocalSourceID], replace: bool, all_: bool):
166
168
  if not code.strip():
167
169
  return
168
170
 
169
- exec(code, {"path": path, "convert": convert, "convert_all": convert_all})
170
-
171
-
172
-
171
+ exec(
172
+ code,
173
+ {"path": path, "convert": convert, "convert_all": convert_all},
174
+ )
173
175
 
174
176
  db = create_database()
175
177
  try:
@@ -218,12 +220,12 @@ def diff(source_id: LocalSourceID):
218
220
 
219
221
  local_files = fm.get_all_files(source_id)
220
222
 
221
- remote_files_should_down = set([
222
- Path(i["path"]) for i in recovers if i["should_download"]
223
- ])
224
- remote_files_filterd = set([
225
- Path(i["path"]) for i in recovers if not i["should_download"]
226
- ])
223
+ remote_files_should_down = set(
224
+ [Path(i["path"]) for i in recovers if i["should_download"]]
225
+ )
226
+ remote_files_filterd = set(
227
+ [Path(i["path"]) for i in recovers if not i["should_download"]]
228
+ )
227
229
  filtered_but_downloaded = remote_files_filterd & local_files
228
230
  should_download_but_missing = remote_files_should_down - local_files
229
231
  added_new_files = (
@@ -288,9 +290,9 @@ def check(list_: bool):
288
290
  continue
289
291
 
290
292
  local_files = fm.get_all_files(source_id)
291
- remote_files_should_down = set([
292
- Path(i["path"]) for i in recovers if i["should_download"]
293
- ])
293
+ remote_files_should_down = set(
294
+ [Path(i["path"]) for i in recovers if i["should_download"]]
295
+ )
294
296
  should_download_but_missing = remote_files_should_down - local_files
295
297
  if len(should_download_but_missing):
296
298
  logger.error(
@@ -8,12 +8,18 @@ def convert_vtt2lrc(vtt_path: Path):
8
8
 
9
9
  assert vtt_path.suffix == ".vtt"
10
10
  lrc_content = vtt2lrc(vtt_path)
11
- with open(vtt_path.with_suffix(".lrc"), "w", encoding="utf-8") as f:
11
+ lrc_path = vtt_path.with_suffix(".lrc")
12
+ if len(vtt_path.suffixes) == 2: # formats like `audio.mp3.vtt`
13
+ assert vtt_path.with_suffix(
14
+ ""
15
+ ).exists(), f"unknown vtt file suffixes, {vtt_path.suffixes}"
16
+ lrc_path = vtt_path.with_suffix("").with_suffix(".lrc")
17
+ with open(lrc_path, "w", encoding="utf-8") as f:
12
18
  f.write(lrc_content)
13
19
 
14
20
 
15
21
  def convert_audio_format(
16
- audio_path: Path, dst: Literal["mp3", "flac","m4a", "wav"] = "mp3"
22
+ audio_path: Path, dst: Literal["mp3", "flac", "m4a", "wav"] = "mp3"
17
23
  ):
18
24
  from subprocess import run
19
25
 
@@ -27,11 +33,11 @@ def convert_audio_format(
27
33
  case "mp3":
28
34
  convert_args = ["mp3", "-ab", "320k"]
29
35
  case "flac":
30
- convert_args = ["flac", '-compression_level', '5']
36
+ convert_args = ["flac", "-compression_level", "5"]
31
37
  case "wav":
32
38
  convert_args = ["pcm_s16le"]
33
39
  case "m4a":
34
- convert_args = ["aac", '-ab', '320k']
40
+ convert_args = ["aac", "-ab", "320k"]
35
41
  case _:
36
42
  assert False
37
43
 
@@ -25,6 +25,7 @@ class Config:
25
25
  mpd_config: "MPDConfig"
26
26
  before_store: str = ""
27
27
 
28
+
28
29
  @dataclass
29
30
  class Filter:
30
31
  regex: str = "" # 正则表达式
@@ -139,7 +139,7 @@ class FileManager:
139
139
  return
140
140
 
141
141
  if hook is not None:
142
- logger.info(f'Execute hook function for: {rj_name}')
142
+ logger.info(f"Execute hook function for: {rj_name}")
143
143
  hook(self.download_path / rj_name)
144
144
 
145
145
  # if os.path.exists(self.storage_path / rj_name):
@@ -385,16 +385,20 @@ class FileManager:
385
385
  def get_all_files(self, source_id: LocalSourceID) -> Set[Path]:
386
386
  """get all files of source ID both in download and storage path"""
387
387
  source_name = id2source_name(source_id)
388
- l1 = set([
389
- i.relative_to(self.download_path / source_name)
390
- for i in (self.download_path / source_name).rglob("*")
391
- if not i.is_dir()
392
- ])
393
- l2 = set([
394
- i.relative_to(self.storage_path / source_name)
395
- for i in (self.storage_path / source_name).rglob("*")
396
- if not i.is_dir()
397
- ])
388
+ l1 = set(
389
+ [
390
+ i.relative_to(self.download_path / source_name)
391
+ for i in (self.download_path / source_name).rglob("*")
392
+ if not i.is_dir()
393
+ ]
394
+ )
395
+ l2 = set(
396
+ [
397
+ i.relative_to(self.storage_path / source_name)
398
+ for i in (self.storage_path / source_name).rglob("*")
399
+ if not i.is_dir()
400
+ ]
401
+ )
398
402
  return l1 | l2
399
403
 
400
404
  @classmethod
@@ -34,7 +34,9 @@ class LRCPlayer(App):
34
34
  OPERATION_FREQ = 0.05
35
35
 
36
36
  def __init__(self, episodes: List[Music], *args, **kwargs):
37
+ episodes = sorted(episodes, key=lambda x: x.path)
37
38
  self.episodes = episodes
39
+ # logger.debug(self.episodes)
38
40
  match config.player:
39
41
  case "pygame":
40
42
  from .player.pygameplayer import PyGamePlayer
@@ -91,6 +91,7 @@ class ASMRDownloadManager(AsyncManager):
91
91
  rate: Tuple[float | None, float | None],
92
92
  sell: Tuple[int | None, int | None],
93
93
  price: Tuple[int | None, int | None],
94
+ duration: Tuple[str | None, str | None],
94
95
  params: BrowseParams,
95
96
  all_: bool,
96
97
  ):
@@ -111,7 +112,12 @@ class ASMRDownloadManager(AsyncManager):
111
112
  filters += [f"$-circle:{nc}$" for nc in no_circle]
112
113
  filters += [f"$-age:{na}$" for na in no_age]
113
114
 
114
- for name, value in (("rate", rate), ("sell", sell), ("price", price)):
115
+ for name, value in (
116
+ ("rate", rate),
117
+ ("sell", sell),
118
+ ("price", price),
119
+ ("duration", duration),
120
+ ):
115
121
  if value[0] is not None:
116
122
  filters.append(f"${name}:{value[0]}$")
117
123
  if value[1] is not None:
@@ -33,7 +33,7 @@ dependencies = [
33
33
  ]
34
34
  requires-python = ">=3.10"
35
35
  readme = "README.md"
36
- version = "2.2.3"
36
+ version = "2.2.5"
37
37
 
38
38
  [project.license]
39
39
  text = "MIT"
@@ -1 +0,0 @@
1
- __version__ = "2.2.3"
File without changes
File without changes