ScriptCollection 3.5.108__py3-none-any.whl → 3.5.110__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.
@@ -628,3 +628,29 @@ def CurrentUserHasElevatedPrivileges() -> int:
628
628
  return 1
629
629
  else:
630
630
  return 0
631
+
632
+
633
+ def Espoc() -> int:
634
+ GeneralUtilities.write_message_to_stdout("check...")
635
+ parser = argparse.ArgumentParser(description="Espoc (appreviation for 'exit started programs on close') is a tool to ensure the started processes of your program will also get terminated when the execution of your program is finished.")
636
+ parser.add_argument('-p', '--processid', required=True)
637
+ parser.add_argument('-f', '--file', required=True, help='Specifies the file where the process-ids of the started processes are stored (line by line). This file will be deleted when all started processes are terminated.')
638
+ args = parser.parse_args()
639
+ process_id = args.processid
640
+ process_list_file: str = args.file
641
+ if not os.path.isabs(process_list_file):
642
+ process_list_file = GeneralUtilities.resolve_relative_path(process_list_file, os.getcwd())
643
+ GeneralUtilities.assert_condition(GeneralUtilities.process_is_running_by_id(process_id), f"Process with id {process_id} is not running.")
644
+ while GeneralUtilities.process_is_running_by_id(process_id):
645
+ time.sleep(1)
646
+ GeneralUtilities.write_message_to_stdout(f"Process with id {process_id} is not running anymore. Start terminating remaining processes.")
647
+ if os.path.exists(process_list_file):
648
+ for line in GeneralUtilities.read_lines_from_file(process_list_file):
649
+ if GeneralUtilities.string_has_content(line):
650
+ current_process_id = int(line.strip())
651
+ GeneralUtilities.kill_process(current_process_id, True)
652
+ GeneralUtilities.ensure_file_does_not_exist(process_list_file)
653
+ GeneralUtilities.write_message_to_stdout("All started processes terminated.")
654
+ else:
655
+ GeneralUtilities.write_message_to_stdout(f"File '{process_list_file}' does not exist. No processes to terminate.")
656
+ return 0
@@ -33,7 +33,7 @@ from .ProgramRunnerBase import ProgramRunnerBase
33
33
  from .ProgramRunnerPopen import ProgramRunnerPopen
34
34
  from .ProgramRunnerEpew import ProgramRunnerEpew, CustomEpewArgument
35
35
 
36
- version = "3.5.108"
36
+ version = "3.5.110"
37
37
  __version__ = version
38
38
 
39
39
 
@@ -603,6 +603,12 @@ class ScriptCollectionCore:
603
603
  self.assert_is_git_repository(repository_folder)
604
604
  return self.run_program_argsasarray("git", ["rev-parse", "--verify", "HEAD"], repository_folder, throw_exception_if_exitcode_is_not_zero=False)[0] == 0
605
605
 
606
+ @GeneralUtilities.check_arguments
607
+ def run_git_command_in_repository_and_submodules(self, repository_folder: str, arguments: list[str]) -> None:
608
+ self.assert_is_git_repository(repository_folder)
609
+ self.run_program_argsasarray("git", arguments, repository_folder)
610
+ self.run_program_argsasarray("git", ["submodule", "foreach", "--recursive", "git"]+arguments, repository_folder)
611
+
606
612
  @GeneralUtilities.check_arguments
607
613
  def export_filemetadata(self, folder: str, target_file: str, encoding: str = "utf-8", filter_function=None) -> None:
608
614
  folder = GeneralUtilities.resolve_relative_path_from_current_working_directory(folder)
@@ -881,7 +887,7 @@ class ScriptCollectionCore:
881
887
 
882
888
  @GeneralUtilities.check_arguments
883
889
  def __create_thumbnails(self, filename: str, fps: str, folder: str, tempname_for_thumbnails: str) -> list[str]:
884
- argument = ['-i', filename, '-r', str(fps), '-vf', 'scale=-1:120', '-vcodec', 'png', f'{tempname_for_thumbnails}-%002d.png']
890
+ argument = ['-i', filename, '-r', fps, '-vf', 'scale=-1:120', '-vcodec', 'png', f'{tempname_for_thumbnails}-%002d.png']
885
891
  self.run_program_argsasarray("ffmpeg", argument, folder, throw_exception_if_exitcode_is_not_zero=True)
886
892
  files = GeneralUtilities.get_direct_files_of_folder(folder)
887
893
  result: list[str] = []
@@ -898,8 +904,17 @@ class ScriptCollectionCore:
898
904
  def __create_thumbnail(self, outputfilename: str, folder: str, length_in_seconds: float, tempname_for_thumbnails: str, amount_of_images: int) -> None:
899
905
  duration = timedelta(seconds=length_in_seconds)
900
906
  info = GeneralUtilities.timedelta_to_simple_string(duration)
901
- rows: int = 5
902
- columns: int = math.ceil(amount_of_images/rows)
907
+ next_square_number = GeneralUtilities.get_next_square_number(amount_of_images)
908
+ root = math.sqrt(next_square_number)
909
+ rows: int = root # 5
910
+ columns: int = root # math.ceil(amount_of_images/rows)
911
+ argument = ['-title', f'"{outputfilename} ({info})"', '-tile', f'{rows}x{columns}', f'{tempname_for_thumbnails}*.png', f'{outputfilename}.png']
912
+ self.run_program_argsasarray("montage", argument, folder, throw_exception_if_exitcode_is_not_zero=True)
913
+
914
+ @GeneralUtilities.check_arguments
915
+ def __create_thumbnail2(self, outputfilename: str, folder: str, length_in_seconds: float, rows: int, columns: int, tempname_for_thumbnails: str, amount_of_images: int) -> None:
916
+ duration = timedelta(seconds=length_in_seconds)
917
+ info = GeneralUtilities.timedelta_to_simple_string(duration)
903
918
  argument = ['-title', f'"{outputfilename} ({info})"', '-tile', f'{rows}x{columns}', f'{tempname_for_thumbnails}*.png', f'{outputfilename}.png']
904
919
  self.run_program_argsasarray("montage", argument, folder, throw_exception_if_exitcode_is_not_zero=True)
905
920
 
@@ -912,7 +927,7 @@ class ScriptCollectionCore:
912
927
  return math.ceil(x * d) / d
913
928
 
914
929
  @GeneralUtilities.check_arguments
915
- def generate_thumbnail(self, file: str, frames_per_second: str, tempname_for_thumbnails: str = None, hook=None) -> None:
930
+ def generate_thumbnail(self, file: str, frames_per_second: float, tempname_for_thumbnails: str = None, hook=None) -> None:
916
931
  if tempname_for_thumbnails is None:
917
932
  tempname_for_thumbnails = "t_"+str(uuid.uuid4())
918
933
 
@@ -923,16 +938,9 @@ class ScriptCollectionCore:
923
938
  preview_files: list[str] = []
924
939
  try:
925
940
  length_in_seconds = self.__calculate_lengh_in_seconds(filename, folder)
926
- if (frames_per_second.endswith("fps")):
927
- # frames per second, example: frames_per_second="20fps" => 20 frames per second
928
- frames_per_second = self.__roundup(float(frames_per_second[:-3]), 2)
929
- frames_per_second_as_string = str(frames_per_second)
930
- amounf_of_previewframes = int(math.floor(length_in_seconds*frames_per_second))
931
- else:
932
- # concrete amount of frame, examples: frames_per_second="16" => 16 frames for entire video
933
- amounf_of_previewframes = int(float(frames_per_second))
934
- # self.roundup((amounf_of_previewframes-2)/length_in_seconds, 2)
935
- frames_per_second_as_string = f"{amounf_of_previewframes-2}/{length_in_seconds}"
941
+ # frames per second, example: frames_per_second="20fps" => 20 frames per second
942
+ frames_per_second = self.__roundup(float(frames_per_second[:-3]), 2)
943
+ frames_per_second_as_string = str(frames_per_second)
936
944
  preview_files = self.__create_thumbnails(filename, frames_per_second_as_string, folder, tempname_for_thumbnails)
937
945
  if hook is not None:
938
946
  hook(file, preview_files)
@@ -942,6 +950,29 @@ class ScriptCollectionCore:
942
950
  for thumbnail_to_delete in preview_files:
943
951
  os.remove(thumbnail_to_delete)
944
952
 
953
+ @GeneralUtilities.check_arguments
954
+ def generate_thumbnail_by_amount_of_pictures(self, file: str, amount_of_columns: int, amount_of_rows: int, tempname_for_thumbnails: str = None, hook=None) -> None:
955
+ if tempname_for_thumbnails is None:
956
+ tempname_for_thumbnails = "t_"+str(uuid.uuid4())
957
+
958
+ file = GeneralUtilities.resolve_relative_path_from_current_working_directory(file)
959
+ filename = os.path.basename(file)
960
+ folder = os.path.dirname(file)
961
+ filename_without_extension = Path(file).stem
962
+ preview_files: list[str] = []
963
+ try:
964
+ length_in_seconds = self.__calculate_lengh_in_seconds(filename, folder)
965
+ amounf_of_previewframes = int(amount_of_columns*amount_of_rows)
966
+ frames_per_second_as_string = f"{amounf_of_previewframes-2}/{length_in_seconds}"
967
+ preview_files = self.__create_thumbnails(filename, frames_per_second_as_string, folder, tempname_for_thumbnails)
968
+ if hook is not None:
969
+ hook(file, preview_files)
970
+ actual_amounf_of_previewframes = len(preview_files)
971
+ self.__create_thumbnail2(filename_without_extension, folder, length_in_seconds, amount_of_rows, amount_of_columns, tempname_for_thumbnails, actual_amounf_of_previewframes)
972
+ finally:
973
+ for thumbnail_to_delete in preview_files:
974
+ os.remove(thumbnail_to_delete)
975
+
945
976
  @GeneralUtilities.check_arguments
946
977
  def extract_pdf_pages(self, file: str, from_page: int, to_page: int, outputfile: str) -> None:
947
978
  pdf_reader: PdfReader = PdfReader(file)
@@ -862,7 +862,7 @@ class TasksForCommonProjectStructure:
862
862
  if os.path.isfile(os.path.join(codeunit_folder, runsettings_file)):
863
863
  arg = f"{arg} --settings {runsettings_file}"
864
864
  arg = f"{arg} /p:CollectCoverage=true /p:CoverletOutput=../Other/Artifacts/TestCoverage/Testcoverage /p:CoverletOutputFormat=cobertura"
865
- self.__sc.run_program("dotnet", arg, codeunit_folder, verbosity=verbosity)
865
+ self.__sc.run_program("dotnet", arg, codeunit_folder, verbosity=verbosity,print_live_output=True)
866
866
  target_file = os.path.join(coverage_file_folder, "TestCoverage.xml")
867
867
  GeneralUtilities.ensure_file_does_not_exist(target_file)
868
868
  os.rename(os.path.join(coverage_file_folder, "Testcoverage.cobertura.xml"), target_file)
@@ -2477,11 +2477,18 @@ class TasksForCommonProjectStructure:
2477
2477
  GeneralUtilities.copy_content_of_folder(ca_source_folder, ca_target_folder)
2478
2478
 
2479
2479
  @GeneralUtilities.check_arguments
2480
- def _internal_get_sorted_codeunits_by_dict(self, codeunits=dict[str, set[str]]) -> list[str]:
2481
- result_typed = list(TopologicalSorter(codeunits).static_order())
2482
- result = list()
2483
- for item in result_typed:
2484
- result.append(str(item))
2480
+ def _internal_get_sorted_codeunits_by_dict(self, codeunits: dict[str, set[str]]) -> list[str]:
2481
+ sorted_codeunits = {
2482
+ node: sorted(codeunits[node])
2483
+ for node in sorted(codeunits)
2484
+ }
2485
+
2486
+ ts = TopologicalSorter()
2487
+ for node, deps in sorted_codeunits.items():
2488
+ ts.add(node, *deps)
2489
+
2490
+ result_typed = list(ts.static_order())
2491
+ result = [str(item) for item in result_typed]
2485
2492
  return result
2486
2493
 
2487
2494
  @GeneralUtilities.check_arguments
@@ -2546,7 +2553,7 @@ class TasksForCommonProjectStructure:
2546
2553
 
2547
2554
  @GeneralUtilities.check_arguments
2548
2555
  def build_specific_codeunits(self, repository_folder: str, codeunits: list[str], verbosity: int = 1, target_environmenttype: str = "QualityCheck", additional_arguments_file: str = None, is_pre_merge: bool = False, export_target_directory: str = None, assume_dependent_codeunits_are_already_built: bool = True, commandline_arguments: list[str] = [], do_git_clean_when_no_changes: bool = False, note: str = None, check_for_new_files: bool = True) -> None:
2549
- codeunits_list = {", ".join(codeunits)}
2556
+ codeunits_list = "{"+", ".join(["a","b"])+"}"
2550
2557
  if verbosity > 2:
2551
2558
  GeneralUtilities.write_message_to_stdout(f"Start building codeunits ({codeunits_list}) in repository '{repository_folder}'...")
2552
2559
  self.__sc.assert_is_git_repository(repository_folder)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ScriptCollection
3
- Version: 3.5.108
3
+ Version: 3.5.110
4
4
  Summary: The ScriptCollection is the place for reusable scripts.
5
5
  Home-page: https://github.com/anionDev/ScriptCollection
6
6
  Author: Marius Göcke
@@ -24,13 +24,14 @@ Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  Requires-Dist: build>=1.2.2.post1
26
26
  Requires-Dist: coverage>=7.8.0
27
- Requires-Dist: cyclonedx-bom>=5.3.0
27
+ Requires-Dist: cyclonedx-bom>=6.0.0
28
28
  Requires-Dist: defusedxml>=0.7.1
29
29
  Requires-Dist: keyboard>=0.13.5
30
30
  Requires-Dist: lcov-cobertura>=2.1.1
31
- Requires-Dist: lxml>=5.3.2
31
+ Requires-Dist: lxml>=5.4.0
32
32
  Requires-Dist: ntplib>=0.4.0
33
33
  Requires-Dist: Pillow>=11.2.1
34
+ Requires-Dist: psutil>=7.0.0
34
35
  Requires-Dist: pycdlib>=1.14.0
35
36
  Requires-Dist: Pygments>=2.19.1
36
37
  Requires-Dist: pylint>=3.3.6
@@ -38,7 +39,7 @@ Requires-Dist: pyOpenSSL>=25.0.0
38
39
  Requires-Dist: PyPDF>=5.4.0
39
40
  Requires-Dist: pytest>=8.3.5
40
41
  Requires-Dist: PyYAML>=6.0.2
41
- Requires-Dist: qrcode>=8.1
42
+ Requires-Dist: qrcode>=8.2
42
43
  Requires-Dist: send2trash>=1.8.3
43
44
  Requires-Dist: twine>=6.1.0
44
45
  Requires-Dist: xmlschema>=4.0.1
@@ -1,16 +1,16 @@
1
1
  ScriptCollection/CertificateUpdater.py,sha256=pJopWFcwaLAEVljtC4O3SVrlpIpoJNUhT1V4mgiqLvE,8970
2
- ScriptCollection/Executables.py,sha256=HI9Pxs5Z9QxPGyqeJU2lWslEggFyGYANCqYVQZp6eJ0,30490
2
+ ScriptCollection/Executables.py,sha256=BsDgpqU_XKXg-7paQdmamzU14-2kcqjz9v13r96EiEk,32319
3
3
  ScriptCollection/GeneralUtilities.py,sha256=VO4a7xctkjN5dcBXc32gz0x9U07DEagasjvYVKqLmdM,44173
4
4
  ScriptCollection/ProcessesRunner.py,sha256=3mu4ZxzZleQo0Op6o9EYTCFiJfb6kx5ov2YfZfT89mU,1395
5
5
  ScriptCollection/ProgramRunnerBase.py,sha256=2kMIAqdc65UjBAddOZkzy_aFx9h5roZ5a4bQNM6RV6Y,2480
6
6
  ScriptCollection/ProgramRunnerEpew.py,sha256=4pjEd0r9Fcz3TTDv0MdTSd5KkigYXcWUVI1X43regfU,6477
7
7
  ScriptCollection/ProgramRunnerPopen.py,sha256=BPY7-ZMIlqT7JOKz8qlB5c0laF2Js-ijzqk09GxZC48,3821
8
8
  ScriptCollection/SCLog.py,sha256=Gw27Oclcb0ten7_89PD5CdNMoO-at2hGUOYbF-x1HPQ,2296
9
- ScriptCollection/ScriptCollectionCore.py,sha256=9R--jXBBIL0wX6Cw7cq9C1UpZvIgQ_qpvf2jcXgqXY8,128371
10
- ScriptCollection/TasksForCommonProjectStructure.py,sha256=SJ7V9HilnkCBJGCCrObWwAORSI_pXW9zC4uon1O6Xbg,232705
9
+ ScriptCollection/ScriptCollectionCore.py,sha256=2RmUIh9GWngFo9ucfNrTfcTYQ5KD7RI4iJOUSPelqsA,130404
10
+ ScriptCollection/TasksForCommonProjectStructure.py,sha256=5GnI89wwy8_uJH_pZtd0zgZ1C0ReIrZeePjUIwo2sus,232910
11
11
  ScriptCollection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- scriptcollection-3.5.108.dist-info/METADATA,sha256=ooO5jOZCS4o095UVS3mMveJO7BUcDqXd5Ijpid7kN_I,7664
13
- scriptcollection-3.5.108.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
14
- scriptcollection-3.5.108.dist-info/entry_points.txt,sha256=fYCGWGNGijBQHhFe6UAO-BEpfEOxLyNJemukt5ElSzs,3644
15
- scriptcollection-3.5.108.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
16
- scriptcollection-3.5.108.dist-info/RECORD,,
12
+ scriptcollection-3.5.110.dist-info/METADATA,sha256=W6tx5MA4Vq71V45HNdsJG1MHJDd9ifbe7OVmgaEm780,7694
13
+ scriptcollection-3.5.110.dist-info/WHEEL,sha256=GHB6lJx2juba1wDgXDNlMTyM13ckjBMKf-OnwgKOCtA,91
14
+ scriptcollection-3.5.110.dist-info/entry_points.txt,sha256=3qMbfZEMhc_VTJj-bcLwB8AWcn9iXSB3l0AWpuu52Bs,3689
15
+ scriptcollection-3.5.110.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
16
+ scriptcollection-3.5.110.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -15,6 +15,7 @@ sccreatehashofallfiles = ScriptCollection.Executables:CreateHashOfAllFiles
15
15
  sccreateisofilewithobfuscatedfiles = ScriptCollection.Executables:CreateISOFileWithObfuscatedFiles
16
16
  sccreatesimplemergewithoutrelease = ScriptCollection.Executables:CreateSimpleMergeWithoutRelease
17
17
  sccurrentuserhaselevatedprivileges = ScriptCollection.Executables:CurrentUserHasElevatedPrivileges
18
+ scespoc = ScriptCollection.Executables:Espoc
18
19
  scextractpdfpages = ScriptCollection.Executables:ExtractPDFPages
19
20
  scfilecontainscontent = ScriptCollection.Executables:FileContainsContent
20
21
  scfileexists = ScriptCollection.Executables:FileExists