ccfx 0.9.0__tar.gz → 1.0.0__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.
- {ccfx-0.9.0/ccfx.egg-info → ccfx-1.0.0}/PKG-INFO +1 -1
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx/ccfx.py +164 -5
- {ccfx-0.9.0 → ccfx-1.0.0/ccfx.egg-info}/PKG-INFO +1 -1
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx.egg-info/SOURCES.txt +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx.egg-info/dependency_links.txt +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx.egg-info/requires.txt +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx.egg-info/top_level.txt +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/pyproject.toml +1 -1
- {ccfx-0.9.0 → ccfx-1.0.0}/setup.cfg +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/LICENSE +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/MANIFEST.in +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/README.md +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx/__init__.py +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx/excel.py +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx/mssqlConnection.py +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx/sqliteConnection.py +0 -0
- {ccfx-0.9.0 → ccfx-1.0.0}/ccfx/word.py +0 -0
@@ -14,7 +14,7 @@ import os, sys
|
|
14
14
|
import glob
|
15
15
|
import warnings
|
16
16
|
from netCDF4 import Dataset
|
17
|
-
from osgeo import gdal, osr
|
17
|
+
from osgeo import gdal, ogr, osr
|
18
18
|
import numpy
|
19
19
|
from genericpath import exists
|
20
20
|
import shutil
|
@@ -23,7 +23,6 @@ import pickle
|
|
23
23
|
import time
|
24
24
|
from shapely.geometry import box, Point
|
25
25
|
import geopandas, pandas
|
26
|
-
from osgeo import gdal, ogr, osr
|
27
26
|
import py7zr
|
28
27
|
import subprocess
|
29
28
|
import multiprocessing
|
@@ -133,7 +132,7 @@ def guessMimeType(imagePath):
|
|
133
132
|
return 'image/png'
|
134
133
|
|
135
134
|
|
136
|
-
def downloadYoutubeVideo(url: str, dstDir: str, audioOnly: bool = False, dstFileName: Optional[str] = None ) -> str:
|
135
|
+
def downloadYoutubeVideo(url: str, dstDir: str, audioOnly: bool = False, cookiesFile: str = None, dstFileName: Optional[str] = None ) -> str:
|
137
136
|
"""
|
138
137
|
Download from YouTube via yt-dlp.
|
139
138
|
|
@@ -154,6 +153,9 @@ def downloadYoutubeVideo(url: str, dstDir: str, audioOnly: bool = False, dstFile
|
|
154
153
|
|
155
154
|
opts = {"outtmpl": template}
|
156
155
|
|
156
|
+
if cookiesFile:
|
157
|
+
opts["cookiefile"] = cookiesFile
|
158
|
+
|
157
159
|
if audioOnly:
|
158
160
|
opts.update({
|
159
161
|
"format": "bestaudio/best",
|
@@ -163,6 +165,7 @@ def downloadYoutubeVideo(url: str, dstDir: str, audioOnly: bool = False, dstFile
|
|
163
165
|
"preferredquality": "192",
|
164
166
|
}],
|
165
167
|
})
|
168
|
+
|
166
169
|
else:
|
167
170
|
# prefer a single MP4 file (progressive), fallback to any best if none
|
168
171
|
opts["format"] = "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"
|
@@ -181,6 +184,58 @@ def downloadYoutubeVideo(url: str, dstDir: str, audioOnly: bool = False, dstFile
|
|
181
184
|
return os.path.join(dstDir, final)
|
182
185
|
|
183
186
|
|
187
|
+
def parseYoutubePlaylist(playlistUrl: str) -> list[str]:
|
188
|
+
"""
|
189
|
+
Return a list of full video URLs contained in a YouTube playlist.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
playlistUrl: Full URL of the playlist (the one with &list=… or /playlist?list=…).
|
193
|
+
|
194
|
+
Returns:
|
195
|
+
List of video URLs in the order reported by YouTube.
|
196
|
+
"""
|
197
|
+
opts = {
|
198
|
+
"quiet": True,
|
199
|
+
"extract_flat": "in_playlist", # don’t recurse into each video
|
200
|
+
}
|
201
|
+
|
202
|
+
with yt_dlp.YoutubeDL(opts) as ytdl:
|
203
|
+
info = ytdl.extract_info(playlistUrl, download=False)
|
204
|
+
|
205
|
+
entries = info.get("entries", [])
|
206
|
+
return [f"https://www.youtube.com/watch?v={e['id']}" for e in entries if e.get("id")]
|
207
|
+
|
208
|
+
|
209
|
+
def parseYoutubeChannelVideos(channelUrl: str, maxItems: Optional[int] = None) -> list[str]:
|
210
|
+
"""
|
211
|
+
Return a list of video URLs published on a channel.
|
212
|
+
|
213
|
+
Args:
|
214
|
+
channelUrl: Any canonical channel URL, e.g.
|
215
|
+
- https://www.youtube.com/@LinusTechTips
|
216
|
+
- https://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw
|
217
|
+
- https://www.youtube.com/c/NASA/videos
|
218
|
+
maxItems: Optional hard limit. If None, returns every video the API exposes.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
List of video URLs, newest-first (YouTube’s default order).
|
222
|
+
"""
|
223
|
+
opts = {
|
224
|
+
"quiet": True,
|
225
|
+
"extract_flat": True, # treat the channel as one big “playlist”
|
226
|
+
"skip_download": True,
|
227
|
+
}
|
228
|
+
|
229
|
+
with yt_dlp.YoutubeDL(opts) as ytdl:
|
230
|
+
info = ytdl.extract_info(channelUrl, download=False)
|
231
|
+
|
232
|
+
entries = info.get("entries", [])
|
233
|
+
if maxItems is not None:
|
234
|
+
entries = entries[:maxItems]
|
235
|
+
|
236
|
+
return [f"https://www.youtube.com/watch?v={e['id']}" for e in entries if e.get("id")]
|
237
|
+
|
238
|
+
|
184
239
|
def setMp3Metadata(fn, metadata, imagePath=None):
|
185
240
|
'''
|
186
241
|
This function takes a path to an mp3 and a metadata dictionary,
|
@@ -276,6 +331,39 @@ def deleteFile(filePath:str, v:bool = False) -> bool:
|
|
276
331
|
|
277
332
|
return deleted
|
278
333
|
|
334
|
+
|
335
|
+
def alert(message:str, server:str = "http://ntfy.sh", topic:str = "pythonAlerts", attachment:str = None, messageTitle:str = "info", priority:int = None, tags:list = [], printIt:bool = True, v:bool = False) -> bool:
|
336
|
+
'''
|
337
|
+
This sends an alert to a given server in case you want to be notified of something
|
338
|
+
message : the message to send
|
339
|
+
server : the server to send the message to (default is http://ntfy.sh)
|
340
|
+
topic : the topic to send the message to (default is pythonAlerts)
|
341
|
+
attachment : a file to attach to the message (optional)
|
342
|
+
messageTitle : the title of the message (optional, default is info)
|
343
|
+
priority : the priority of the message (optional, default is None)
|
344
|
+
tags : a list of tags to add to the message (optional, default is empty list)
|
345
|
+
printIt : whether to print the message to the console (default is True)
|
346
|
+
v : verbose (default is False, set to True to print debug info)
|
347
|
+
|
348
|
+
return: True if the alert was sent successfully, False otherwise
|
349
|
+
'''
|
350
|
+
print(message) if printIt else None; header_data = {}
|
351
|
+
if not messageTitle is None: header_data["Title"] = messageTitle
|
352
|
+
if not priority is None: header_data["Priority"] = priority
|
353
|
+
if not len(tags) == 0: header_data["Tags"] = ",".join(tags)
|
354
|
+
|
355
|
+
try:
|
356
|
+
if v: print(f"sending alert to {server}/{topic}")
|
357
|
+
if not attachment is None:
|
358
|
+
header_data["Filename"] = getFileBaseName(attachment)
|
359
|
+
requests.put( f"{server}/{topic}", data=open(attachment, 'rb'), headers=header_data )
|
360
|
+
return True
|
361
|
+
try: requests.post(f"{server}/{topic}",data=message, headers=header_data )
|
362
|
+
except: return False
|
363
|
+
except: return False
|
364
|
+
return True
|
365
|
+
|
366
|
+
|
279
367
|
def deletePath(path:str, v:bool = False) -> bool:
|
280
368
|
'''
|
281
369
|
Delete a directory
|
@@ -309,6 +397,38 @@ def downloadChunk(url, start, end, path):
|
|
309
397
|
if chunk:
|
310
398
|
f.write(chunk)
|
311
399
|
|
400
|
+
|
401
|
+
def formatStringBlock(input_str, max_chars=70):
|
402
|
+
'''
|
403
|
+
This function takes a string and formats it into a block of text
|
404
|
+
with a maximum number of characters per line.
|
405
|
+
|
406
|
+
input_str: the string to format
|
407
|
+
max_chars: the maximum number of characters per line (default is 70)
|
408
|
+
|
409
|
+
'''
|
410
|
+
words = input_str.split(' ')
|
411
|
+
lines = []
|
412
|
+
current_line = ""
|
413
|
+
|
414
|
+
for word in words:
|
415
|
+
# If adding the next word to the current line would exceed the max_chars limit
|
416
|
+
if len(current_line) + len(word) > max_chars:
|
417
|
+
# Append current line to lines and start a new one
|
418
|
+
lines.append(current_line.strip())
|
419
|
+
current_line = word
|
420
|
+
else:
|
421
|
+
# Add the word to the current line
|
422
|
+
current_line += " " + word
|
423
|
+
|
424
|
+
# Append any remaining words
|
425
|
+
lines.append(current_line.strip())
|
426
|
+
|
427
|
+
return '\n'.join(lines)
|
428
|
+
|
429
|
+
|
430
|
+
|
431
|
+
|
312
432
|
def downloadFile(url, save_path, exists_action='resume', num_connections=5, v=False):
|
313
433
|
if v:
|
314
434
|
print(f"\ndownloading {url}")
|
@@ -643,21 +763,45 @@ def renameNetCDFvariable(input_file: str, output_file: str, old_var_name: str, n
|
|
643
763
|
except subprocess.CalledProcessError as e:
|
644
764
|
print(f"Error: {e.stderr}")
|
645
765
|
|
646
|
-
|
766
|
+
|
767
|
+
def compressTo7z(input_dir: str, output_file: str, compressionLevel: int = 4, excludeExt: list = None, v: bool = False) -> None:
|
647
768
|
"""
|
648
769
|
Compresses the contents of a directory to a .7z archive with maximum compression.
|
649
770
|
|
650
771
|
:param input_dir: Path to the directory to compress
|
651
772
|
:param output_file: Output .7z file path
|
773
|
+
:param compressionLevel: Compression level (0-9), default is 4 (maximum compression)
|
774
|
+
:param excludeExt: List of file extensions to exclude from compression
|
652
775
|
"""
|
776
|
+
if excludeExt is None:
|
777
|
+
excludeExt = []
|
778
|
+
|
653
779
|
# Create the .7z archive with LZMA2 compression
|
654
|
-
with py7zr.SevenZipFile(output_file, 'w', filters=[{'id': py7zr.FILTER_LZMA2, 'preset':
|
780
|
+
with py7zr.SevenZipFile(output_file, 'w', filters=[{'id': py7zr.FILTER_LZMA2, 'preset': compressionLevel}]) as archive:
|
655
781
|
# Add each item in the input directory, avoiding the top-level folder in the archive
|
656
782
|
for root, _, files in os.walk(input_dir):
|
657
783
|
for file in files:
|
658
784
|
file_path = os.path.join(root, file)
|
785
|
+
|
786
|
+
# Skip excluded file extensions
|
787
|
+
if any(file.endswith(ext) for ext in excludeExt):
|
788
|
+
continue
|
659
789
|
# Add file to the archive with a relative path to avoid including the 'tmp' folder itself
|
660
790
|
archive.write(file_path, arcname=os.path.relpath(file_path, start=input_dir))
|
791
|
+
if v:
|
792
|
+
print(f"compressed {input_dir} to {output_file} with compression level {compressionLevel}.")
|
793
|
+
|
794
|
+
|
795
|
+
def uncompress(inputFile: str, outputDir: str, v: bool = False) -> None:
|
796
|
+
"""
|
797
|
+
Extracts an archive supported by py7zr (.7z, .zip, .tar, .tar.gz, .tar.bz2, .xz, .tar.xz) to outputDir.
|
798
|
+
inputFile: Path to the input archive file
|
799
|
+
outputDir: Directory where the contents will be extracted
|
800
|
+
v: Verbose flag to print extraction status (default is False)
|
801
|
+
"""
|
802
|
+
if not exists(outputDir): createPath(outputDir)
|
803
|
+
with py7zr.SevenZipFile(inputFile, 'r') as archive: archive.extractall(path=outputDir)
|
804
|
+
if v: print(f"extracted {inputFile} to {outputDir}.")
|
661
805
|
|
662
806
|
|
663
807
|
def moveDirectory(srcDir:str, destDir:str, v:bool = False) -> bool:
|
@@ -744,6 +888,21 @@ def clipRasterByExtent(inFile: str, outFile: str, bounds: tuple) -> str:
|
|
744
888
|
ds = None
|
745
889
|
return outFile
|
746
890
|
|
891
|
+
|
892
|
+
def clipRasterByVector(inFile: str, outFile: str, vectorFile: str) -> str:
|
893
|
+
'''
|
894
|
+
Clips a raster using GDAL warp with a vector file
|
895
|
+
inFile: input raster path
|
896
|
+
outFile: output path
|
897
|
+
vectorFile: vector file path (e.g., shapefile or GeoJSON)
|
898
|
+
return: output path
|
899
|
+
'''
|
900
|
+
ds = gdal.Open(inFile)
|
901
|
+
gdal.Warp(outFile, ds, cutlineDSName=vectorFile, cropToCutline=True)
|
902
|
+
ds = None
|
903
|
+
return outFile
|
904
|
+
|
905
|
+
|
747
906
|
def clipVectorByExtent(inFile: str, outFile: str, bounds: tuple) -> str:
|
748
907
|
'''
|
749
908
|
Clips a vector using GeoPandas
|
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
|
File without changes
|
File without changes
|