talklib 2.2.1__tar.gz → 3.1.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.
- {talklib-2.2.1/src/talklib.egg-info → talklib-3.1.0}/PKG-INFO +1 -1
- {talklib-2.2.1 → talklib-3.1.0}/pyproject.toml +5 -1
- talklib-3.1.0/src/talklib/cli.py +88 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib/pod.py +50 -9
- {talklib-2.2.1 → talklib-3.1.0/src/talklib.egg-info}/PKG-INFO +1 -1
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib.egg-info/SOURCES.txt +2 -0
- talklib-3.1.0/src/talklib.egg-info/entry_points.txt +2 -0
- {talklib-2.2.1 → talklib-3.1.0}/LICENSE.txt +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/README.md +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/requirements.txt +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/setup.cfg +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib/__init__.py +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib/ev.py +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib/ffmpeg.py +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib/notify.py +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib/show.py +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib/utils.py +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib.egg-info/dependency_links.txt +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib.egg-info/requires.txt +0 -0
- {talklib-2.2.1 → talklib-3.1.0}/src/talklib.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "talklib"
|
|
3
|
-
version = "
|
|
3
|
+
version = "3.1.0"
|
|
4
4
|
description = "A package to automate processing of shows/segments airing on the TL"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {file = "LICENSE.txt"}
|
|
@@ -23,3 +23,7 @@ build-backend = "setuptools.build_meta"
|
|
|
23
23
|
where = ["src"]
|
|
24
24
|
include = ["talklib*"]
|
|
25
25
|
exclude = [".tests*"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
talklib = "talklib.cli:main"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import xml.etree.ElementTree as ET
|
|
5
|
+
|
|
6
|
+
from talklib.pod import SSH
|
|
7
|
+
|
|
8
|
+
def parse_args():
|
|
9
|
+
parser = argparse.ArgumentParser(description="use talklib in the terminal")
|
|
10
|
+
|
|
11
|
+
feed_template_help = "Generate an RSS feed template in the current directory"
|
|
12
|
+
parser.add_argument('--feed-template', action='store_true', help=feed_template_help, required=False)
|
|
13
|
+
|
|
14
|
+
new_podcast_directory_help = "Creates a new podcast directory on the server with the value you pass in. \
|
|
15
|
+
You MUST have an RSS feed (feed.xml) and logo (image.jpg) in the current directory."
|
|
16
|
+
parser.add_argument('--new-pod-dir', type=str, help=new_podcast_directory_help, required=False)
|
|
17
|
+
|
|
18
|
+
args = parser.parse_args()
|
|
19
|
+
|
|
20
|
+
return args
|
|
21
|
+
|
|
22
|
+
def generate_feed_template():
|
|
23
|
+
ET.register_namespace(prefix="atom", uri="http://www.w3.org/2005/Atom")
|
|
24
|
+
ET.register_namespace(prefix="itunes", uri="http://www.itunes.com/dtds/podcast-1.0.dtd")
|
|
25
|
+
feed = ET.fromstring(feed_template)
|
|
26
|
+
tree = ET.ElementTree(feed)
|
|
27
|
+
tree.write("feed.xml", encoding="utf-8", xml_declaration=True)
|
|
28
|
+
# feed.write("feed.xml", encoding="utf-8", xml_declaration=True)
|
|
29
|
+
print("'feed.xml' file created in " + os.getcwd())
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
def new_podcast_dir(name: str):
|
|
33
|
+
if not os.path.isfile("feed.xml"):
|
|
34
|
+
return print(f"cannot find 'feed.xml' in {os.getcwd()}. You must have this file in the current directory.")
|
|
35
|
+
|
|
36
|
+
if not os.path.isfile("image.jpg"):
|
|
37
|
+
return print(f"cannot find 'image.jpg' in {os.getcwd()}. You must have this file in the current directory.")
|
|
38
|
+
|
|
39
|
+
print("generating new podcast directory called " + name)
|
|
40
|
+
|
|
41
|
+
ssh = SSH()
|
|
42
|
+
ssh.make_new_folder(folder=name)
|
|
43
|
+
ssh.upload_file(file="feed.xml", folder=name)
|
|
44
|
+
ssh.upload_file(file="image.jpg", folder=name)
|
|
45
|
+
|
|
46
|
+
print("done! you can now delete the local copies of the files")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
def main():
|
|
50
|
+
if len(sys.argv) == 1: # if no arguments are passed
|
|
51
|
+
return print("talklib -h for help")
|
|
52
|
+
args = parse_args()
|
|
53
|
+
|
|
54
|
+
if args.feed_template:
|
|
55
|
+
generate_feed_template()
|
|
56
|
+
if args.new_pod_dir:
|
|
57
|
+
new_podcast_dir(name=args.new_pod_dir)
|
|
58
|
+
|
|
59
|
+
feed_template: str = """
|
|
60
|
+
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
|
|
61
|
+
<channel>
|
|
62
|
+
<title>!!CHANGEME!!</title>
|
|
63
|
+
<link>http://nashvilletalkinglibrary.org/</link>
|
|
64
|
+
<atom:link href="https://assets.library.nashville.org/talkinglibrary/shows/!!CHANGEME!!/feed.xml" rel="self" type="application/rss+xml" />
|
|
65
|
+
<description>
|
|
66
|
+
!!CHANGEME!!
|
|
67
|
+
</description>
|
|
68
|
+
<copyright>© 1975-2022 Nashville Talking Library - Do not copy or redistribute</copyright>
|
|
69
|
+
<docs>https://cyber.harvard.edu/rss/rss.html</docs>
|
|
70
|
+
<generator>NTL Python Magic</generator>
|
|
71
|
+
<webMaster>nashvilletalkinglibrary@gmail.com (Darth Vader)</webMaster>
|
|
72
|
+
<itunes:owner>
|
|
73
|
+
<itunes:name>Nashville Talking Library</itunes:name>
|
|
74
|
+
<itunes:email>ntl@nashville.gov</itunes:email>
|
|
75
|
+
</itunes:owner>
|
|
76
|
+
<itunes:category text="Education" />
|
|
77
|
+
<itunes:explicit>true</itunes:explicit>
|
|
78
|
+
<itunes:image href="https://assets.library.nashville.org/talkinglibrary/shows/!!CHANGEME!!/image.jpg" />
|
|
79
|
+
<image>
|
|
80
|
+
<url>https://assets.library.nashville.org/talkinglibrary/shows/!!CHANGEME!!/image.jpg</url>
|
|
81
|
+
<title>!!CHANGEME!!</title>
|
|
82
|
+
<link>http://nashvilletalkinglibrary.org/</link>
|
|
83
|
+
</image>
|
|
84
|
+
<language>en</language>
|
|
85
|
+
<lastBuildDate>leave me alone</lastBuildDate>
|
|
86
|
+
</channel>
|
|
87
|
+
</rss>
|
|
88
|
+
"""
|
|
@@ -4,12 +4,14 @@ import glob
|
|
|
4
4
|
import math
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
+
import subprocess
|
|
7
8
|
import time
|
|
8
9
|
from typing import ClassVar, Type
|
|
9
10
|
|
|
10
11
|
from fabric import Connection, Result
|
|
11
12
|
from paramiko.ssh_exception import SSHException as paramiko_SSH_exception
|
|
12
13
|
from pydantic import BaseModel, Field, model_validator
|
|
14
|
+
import requests
|
|
13
15
|
|
|
14
16
|
from talklib.ev import EV
|
|
15
17
|
from talklib.notify import Notify
|
|
@@ -90,6 +92,12 @@ The error from the SSH library is: {e}"
|
|
|
90
92
|
self.notifications.send_notifications(
|
|
91
93
|
message=f"Unable to delete '{file}' from {folder}: {e}. Continuing automation...",
|
|
92
94
|
subject="Error")
|
|
95
|
+
|
|
96
|
+
def make_new_folder(self, folder: str) -> None:
|
|
97
|
+
if self.check_folder_exists_no_exception(folder=folder):
|
|
98
|
+
raise Exception (f"{folder}/ already exists on server! Exiting...")
|
|
99
|
+
self.connection.run(f"cd shows && mkdir {folder}", hide=True)
|
|
100
|
+
self.notifications.prep_syslog(message=f"{folder}/ created on server")
|
|
93
101
|
|
|
94
102
|
def get_folders(self) -> list:
|
|
95
103
|
ret_val: list = []
|
|
@@ -135,6 +143,13 @@ The error from the SSH library is: {e}"
|
|
|
135
143
|
self.notifications.send_notifications(message=to_send, subject='Error')
|
|
136
144
|
raise Exception (to_send)
|
|
137
145
|
|
|
146
|
+
def check_folder_exists_no_exception(self, folder: str):
|
|
147
|
+
self.notifications.prep_syslog(message=f"checking if {folder}/ exists on server...")
|
|
148
|
+
folders = self.get_folders()
|
|
149
|
+
if folder.lower() in folders:
|
|
150
|
+
self.notifications.prep_syslog(message=f"{folder}/ exists on server!")
|
|
151
|
+
return True
|
|
152
|
+
|
|
138
153
|
|
|
139
154
|
class Episode(BaseModel):
|
|
140
155
|
feed_file: str = Field(min_length=1, default=None)
|
|
@@ -405,15 +420,13 @@ class TLPod(BaseModel):
|
|
|
405
420
|
output_filename = output_filename[0]
|
|
406
421
|
output_filename = os.path.basename(output_filename).lower()
|
|
407
422
|
output_filename = output_filename + '.mp3'
|
|
408
|
-
self.notifications.prep_syslog(message=f"
|
|
423
|
+
self.notifications.prep_syslog(message=f"final audio file will be {output_filename}")
|
|
409
424
|
return output_filename
|
|
410
425
|
|
|
411
426
|
def convert(self, file:str):
|
|
412
|
-
output_filename = self.create_converted_filename(file=file)
|
|
413
|
-
|
|
414
427
|
self.ffmpeg.input_file = file
|
|
415
|
-
self.ffmpeg.output_file =
|
|
416
|
-
self.ffmpeg.compression = False # this is for podcasts. these files should already be edited
|
|
428
|
+
self.ffmpeg.output_file = "out.mp3"
|
|
429
|
+
self.ffmpeg.compression = False # this is for podcasts. these files should already be edited
|
|
417
430
|
|
|
418
431
|
ffmpeg_commands = self.ffmpeg.get_commands()
|
|
419
432
|
self.notifications.prep_syslog(message=f"FFmppeg commands: {ffmpeg_commands}")
|
|
@@ -426,6 +439,32 @@ class TLPod(BaseModel):
|
|
|
426
439
|
except Exception as ffmpeg_exception:
|
|
427
440
|
self.notifications.send_notifications(message=f'FFmpeg error: {ffmpeg_exception}', subject='Error')
|
|
428
441
|
raise ffmpeg_exception
|
|
442
|
+
|
|
443
|
+
def concat(self, preroll:str, program_audio: str, output_filename: str):
|
|
444
|
+
self.notifications.prep_syslog(message="concatenating preroll and program audio together...")
|
|
445
|
+
subprocess.run(f'ffmpeg -hide_banner -loglevel quiet -i "concat:{preroll}|{program_audio}" -c copy {output_filename}')
|
|
446
|
+
self.notifications.prep_syslog(message=f"files successfully concatenated as: {output_filename}")
|
|
447
|
+
return output_filename
|
|
448
|
+
|
|
449
|
+
def download_preroll(self):
|
|
450
|
+
download_URL = "https://assets.library.nashville.org/talkinglibrary/pod_preroll.mp3"
|
|
451
|
+
input_file = 'preroll.mp3'
|
|
452
|
+
with open (input_file, mode='wb') as downloaded_file:
|
|
453
|
+
self.notifications.prep_syslog(message=f"downloading preroll audio from {download_URL}...")
|
|
454
|
+
a = requests.get(download_URL)
|
|
455
|
+
downloaded_file.write(a.content)
|
|
456
|
+
downloaded_file.close()
|
|
457
|
+
self.notifications.prep_syslog(message="preroll audio successfully downloaded")
|
|
458
|
+
return downloaded_file.name
|
|
459
|
+
|
|
460
|
+
def post_cleaup(self):
|
|
461
|
+
self.notifications.prep_syslog(message="looking for local files to delete...")
|
|
462
|
+
XML_files = glob.glob("*.xml")
|
|
463
|
+
for file in XML_files:
|
|
464
|
+
self.delete_local_file(file=file)
|
|
465
|
+
MP3_files = glob.glob("*.mp3")
|
|
466
|
+
for file in MP3_files:
|
|
467
|
+
self.delete_local_file(file=file)
|
|
429
468
|
|
|
430
469
|
def delete_local_file(self, file: str):
|
|
431
470
|
try:
|
|
@@ -444,11 +483,14 @@ class TLPod(BaseModel):
|
|
|
444
483
|
|
|
445
484
|
audio_file = self.match_file()
|
|
446
485
|
converted_file = self.convert(file=audio_file)
|
|
486
|
+
output_filename = self.create_converted_filename(file=audio_file)
|
|
487
|
+
preroll = self.download_preroll()
|
|
488
|
+
concat_file = self.concat(preroll=preroll, program_audio=converted_file, output_filename=output_filename)
|
|
447
489
|
|
|
448
490
|
feed_file = self.ssh.download_file(folder=self.bucket_folder, file='feed.xml') # all XML files on server should have the same name
|
|
449
491
|
|
|
450
492
|
self.episode.feed_file = feed_file
|
|
451
|
-
self.episode.audio_filename =
|
|
493
|
+
self.episode.audio_filename = concat_file
|
|
452
494
|
self.episode.bucket_folder = self.bucket_folder
|
|
453
495
|
self.episode.episode_title = self.display_name
|
|
454
496
|
self.episode.max_episodes = self.max_episodes_in_feed
|
|
@@ -456,10 +498,9 @@ class TLPod(BaseModel):
|
|
|
456
498
|
self.episode.add_new_episode()
|
|
457
499
|
self.episode.remove_old_episodes()
|
|
458
500
|
|
|
459
|
-
self.ssh.upload_file(folder=self.bucket_folder, file=
|
|
501
|
+
self.ssh.upload_file(folder=self.bucket_folder, file=concat_file)
|
|
460
502
|
self.ssh.upload_file(folder=self.bucket_folder, file=feed_file)
|
|
461
503
|
|
|
462
|
-
self.
|
|
463
|
-
self.delete_local_file(file=converted_file)
|
|
504
|
+
self.post_cleaup()
|
|
464
505
|
|
|
465
506
|
self.notifications.prep_syslog(message="All done.")
|
|
@@ -3,6 +3,7 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
requirements.txt
|
|
5
5
|
src/talklib/__init__.py
|
|
6
|
+
src/talklib/cli.py
|
|
6
7
|
src/talklib/ev.py
|
|
7
8
|
src/talklib/ffmpeg.py
|
|
8
9
|
src/talklib/notify.py
|
|
@@ -12,5 +13,6 @@ src/talklib/utils.py
|
|
|
12
13
|
src/talklib.egg-info/PKG-INFO
|
|
13
14
|
src/talklib.egg-info/SOURCES.txt
|
|
14
15
|
src/talklib.egg-info/dependency_links.txt
|
|
16
|
+
src/talklib.egg-info/entry_points.txt
|
|
15
17
|
src/talklib.egg-info/requires.txt
|
|
16
18
|
src/talklib.egg-info/top_level.txt
|
|
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
|