551 lines
21 KiB
Python
Executable File
551 lines
21 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import sys, os, datetime, fnmatch, glob, random, time, pathlib, re
|
|
from datetime import timedelta
|
|
from os.path import join
|
|
from tinytag import TinyTag
|
|
from random import shuffle
|
|
import sqlite3, json
|
|
import uuid
|
|
from pypika import Query, Table, Field, Column
|
|
|
|
#from mk_web import *
|
|
|
|
path = "/home/rob/antena/"
|
|
show_name = "The_SLO_Indie"
|
|
show_short_description = "An eclectic selection of independent and experimental music from Slovenia. \
|
|
Published weekly by The Rizoma Institute. Hosted and compiled by Rob Canning. \
|
|
Broadcast in Slovenia on FM by Mariborski radio student - MARŠ"
|
|
episode_author="Rob Canning"
|
|
episode_number = int(sys.argv[1])
|
|
input_date = sys.argv[2]
|
|
episode_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
|
episode_duration = 0
|
|
complete_playlist = []
|
|
episode_playlist = []
|
|
show_cover = ""
|
|
archive = []
|
|
artists_played = []
|
|
artist_abreviated = []
|
|
|
|
web_path = "/home/rob/antena/html/episode/{0}/img".format(episode_number)
|
|
|
|
if os.path.exists(web_path):
|
|
print("path_exists_doing_nothing")
|
|
else: os.makedirs(web_path)
|
|
|
|
# /////////////////////////////////////////////////
|
|
|
|
def set_episode_date(input_date):
|
|
global episode_date
|
|
date_str = input_date
|
|
date_format = '%Y-%m-%d'
|
|
episode_date = str(datetime.datetime.strptime(date_str, date_format))
|
|
#return episode_date
|
|
print(episode_date)
|
|
|
|
conn = ''
|
|
def database_create_episodes_table():
|
|
# the show database
|
|
global conn
|
|
conn = sqlite3.connect("database/show.db")
|
|
|
|
q = Query \
|
|
.create_table("EPISODES") \
|
|
.columns(
|
|
Column("id", "INT", nullable=True),
|
|
Column("episode", "INT", nullable=True),
|
|
Column("date", 'DATETIME', nullable=True),
|
|
Column("album", "VARCHAR(200)", nullable=True),
|
|
Column("track", "VARCHAR(120)", nullable=True),
|
|
Column("artist", "VARCHAR(120)", nullable=True),
|
|
Column("trackdur", "FLOAT", nullable=True),
|
|
Column("genre", "VARCHAR(120)", nullable=True),
|
|
Column("year", 'DATETIME', nullable=True),
|
|
Column("path", "VARCHAR(120)", nullable=False))\
|
|
.primary_key("path")
|
|
#.unique("path") \
|
|
#TODO get the unique path back into action find bug
|
|
conn.execute(str(q))
|
|
|
|
|
|
|
|
|
|
print("EPISODES Table created successfully");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_intro(episode_playlist):
|
|
intropath = path + "audio/texts/clips/this_is"
|
|
intro = random.choice(os.listdir(intropath))
|
|
episode_playlist.insert(0, str(os.path.abspath(intropath)) + "/" + str(intro))
|
|
|
|
def add_to_tracks_played(add_to_played: str):
|
|
with open('playlists/track_playout_history.txt', "a") as tracks_played_file:
|
|
tracks_played_file.write(str(add_to_played) + "\n") # newline \n needed here?
|
|
|
|
def check_archive(track):
|
|
global archive
|
|
with open('playlists/track_playout_history.txt') as archive_file:
|
|
for line in archive_file:
|
|
archive.append(line)
|
|
if track not in archive:
|
|
print("____ TRACK NOT YET PLAYED ... ADDING _____")
|
|
return True
|
|
else:
|
|
print("____ TRACK ALREADY PLAYED _____")
|
|
return False
|
|
|
|
def create_episode_playlist(episode_playlist: list, complete_playlist:list):
|
|
|
|
global episode_duration
|
|
global archive
|
|
|
|
track_count = 0
|
|
global artists_played
|
|
max_track_dur = 9
|
|
min_track_dur = 2
|
|
|
|
# TODO what is most important 12 tracks or 60 minutes
|
|
# what will
|
|
while episode_duration < 60 * 60 and track_count < 12 :
|
|
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT * FROM MUSIC_LIBRARY ORDER BY RANDOM() LIMIT 1 ;")
|
|
r = cursor.fetchone() # FETCH ONE RANDOM TRACK FROM THE DATABASE
|
|
print(str(r)+"ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss")
|
|
# for t in r:
|
|
song = str(r[9])
|
|
track_label = str(r[1])
|
|
track_album = str(r[2])
|
|
track_title = str(r[3])
|
|
track_artist = str(r[4])
|
|
track_duration = float(r[6])
|
|
track_genre = str(r[5])
|
|
track_year = str(r[7])
|
|
track_path = '/'.join(song.split('/')[0:-1])
|
|
|
|
# SOME LOGIC TO SEE IF WE ALLOW THAT TRACK OR NOT
|
|
|
|
|
|
# TODO here we need to append to DB not the static file
|
|
if track_artist not in artists_played:
|
|
if check_archive(track_title) is True:
|
|
if track_duration > min_track_dur * 60:
|
|
if int(track_duration) < max_track_dur * 60:
|
|
episode_playlist.append(song.rstrip()) # if 'not in' is true then add the song
|
|
art = string=re.sub("\(.*?\)","",track_artist)
|
|
print(art + "?????????????????????????????????????????????????????????????????????????????????????")
|
|
# shorten verbose artist names such as trojnik Trojnik (Cene Resnik, Tomaž Grom, Vid Drašler)
|
|
art = string=re.sub("and","&",art)
|
|
artist_abreviated.append(art)
|
|
artists_played.append(track_artist) # and add the artist to the played list
|
|
add_to_tracks_played(track_title) # and write entry to archive file
|
|
|
|
if not track_year: # where missing metadata give a dummy value
|
|
track_year = 0000
|
|
|
|
|
|
e = Table('EPISODES')
|
|
q = e.insert(1, episode_number, episode_date, \
|
|
track_album, track_title, track_artist, \
|
|
track_duration,track_genre, track_year, track_path)
|
|
|
|
cursor.execute(str(q))
|
|
# ID, EPISODE, DATE, ALBUM, TRACK, ARTIST, TRACKDUR, YEAR, PATH) \
|
|
# VALUES (NULL, ?, ?, ?, ?, ?,?,?,? )", [episode_number, episode_date, \
|
|
# track_album, track_title, track_artist, \
|
|
# track_duration, track_year, track_path]);
|
|
conn.commit()
|
|
print("sqlite: Episode track successfully inserted into SHOW table");
|
|
|
|
track_count += 1; print(track_count)
|
|
episode_duration = episode_duration + track_duration
|
|
else: print("TRACK TOO SHORT..........." )
|
|
else: print("TRACK TOO LONG..........." )
|
|
else: print("SONG PLAYED IN PREVIOUS EPISODE" )
|
|
else: print("ARTIST ALREADY IN PODCAST")
|
|
|
|
#
|
|
|
|
episode_duration = timedelta(seconds=round(episode_duration))
|
|
|
|
print("total tracks = {0} \n total duration = {1} ".format(track_count, episode_duration))
|
|
|
|
return episode_playlist, episode_duration
|
|
|
|
def combine_images(columns, space, images, variants:int):
|
|
global show_cover
|
|
|
|
from PIL import Image
|
|
from PIL import ImageDraw
|
|
from PIL import ImageFont
|
|
|
|
rows = len(images) // columns
|
|
|
|
if len(images) % columns:
|
|
rows += 1
|
|
|
|
width_max = 500 #max([Image.open(image).width for image in images])
|
|
height_max = 500# max([Image.open(image).height for image in images])
|
|
background_width = width_max*columns + (space*columns)-space
|
|
background_height = height_max*rows + (space*rows)-space
|
|
#background = Image.new('RGBA', (background_width, background_height), (0, 0, 0, 255))
|
|
background = Image.new('RGBA', (width_max*columns , height_max*columns), (0, 0, 0, 255))
|
|
|
|
x = 0
|
|
y = 0
|
|
|
|
for i, image in enumerate(images):
|
|
imga = Image.open(image)
|
|
size = (500,500)
|
|
img = imga.resize(size)
|
|
#img = img.rotate(random.randrange(360))
|
|
x_offset = int((width_max-img.width)/2)
|
|
y_offset = int((height_max-img.height)/2)
|
|
background.paste(img, (x+x_offset, y+y_offset+100))
|
|
x += width_max + space
|
|
if (i+1) % columns == 0:
|
|
y += height_max + space
|
|
x = 0
|
|
|
|
im = ImageDraw.Draw(background)
|
|
mf_h1 = ImageFont.truetype('fonts/Antonio-Light.ttf', 280)
|
|
mf_h2 = ImageFont.truetype('fonts/Antonio-Light.ttf', 65)
|
|
mf_h3 = ImageFont.truetype('fonts/Antonio-Regular.ttf', 50)
|
|
mf_h4 = ImageFont.truetype('fonts/Antonio-Light.ttf', 50)
|
|
|
|
h2_spc = 85
|
|
h2_baseline = 1530
|
|
|
|
# Add Text to the image ----------------------------------------
|
|
|
|
# some logic to shuffle the list if sub sections of list are too long for layout
|
|
str_length_thresh = 50
|
|
|
|
#TODO if an artist is listed as Various Arist then remove it from cover display and think of logic
|
|
#TODO exit while loop if it cant find a solution and restart script or shrink font and adjust rules
|
|
while \
|
|
[len(s) for s in [''.join(artist_abreviated[0:3])]][0] > str_length_thresh or\
|
|
[len(s) for s in [''.join(artist_abreviated[3:6])]][0] > str_length_thresh or\
|
|
[len(s) for s in [''.join(artist_abreviated[6:9])]][0] > str_length_thresh or \
|
|
[len(s) for s in [''.join(artist_abreviated[9:12])]][0] > str_length_thresh:
|
|
print('''one of the lines is longer than fits the page...
|
|
...................shuffling the list for a better look: {0}'''.format(artist_abreviated))
|
|
|
|
random.shuffle(artist_abreviated)
|
|
|
|
im.text((30,10), '''an eclectic selection of contemporary independent music from slovenia: {0} - E P I S O D E #{1}
|
|
'''\
|
|
.format(episode_date,episode_number), fill="white", font=mf_h3)
|
|
im.text((30,280), ''' THIS WEEK ON \n EPISODE #{0} of \n {1}!'''.upper()\
|
|
.format(episode_number, show_name), fill="white", font=mf_h1, stroke_width=2, stroke_fill='black')
|
|
im.text((30, h2_baseline + (h2_spc*1) ), '''m u s i c _ f r o m _ : {0}'''\
|
|
.format(' | '.join(artist_abreviated[0:3])), (255,255,255), font=mf_h2)
|
|
im.text((30, h2_baseline + (h2_spc*2) ), "{0}"\
|
|
.format(' | '.join(artist_abreviated[3:6])), (255,255,255), font=mf_h2)
|
|
im.text((30, h2_baseline + (h2_spc*3)), "{0}"\
|
|
.format(' | '.join(artist_abreviated[6:9])), (255,255,255), font=mf_h2)
|
|
im.text((30, h2_baseline + (h2_spc*4)), "{0}"\
|
|
.format(' | '.join(artist_abreviated[9:12])), (255,255,255), font=mf_h2)
|
|
|
|
im.text((1540,1888), ''' http://{0}.rizom.si '''\
|
|
.format(show_name, episode_date,episode_number), fill="white", font=mf_h4)
|
|
|
|
show_cover = 'img/cover.png'.format(episode_number,episode_date, variants)
|
|
background.save("html/" + "episode/{0}/{1}".format(episode_number, show_cover))
|
|
return show_cover
|
|
|
|
def create_show_coverart(episode_playlist, variants):
|
|
|
|
show_cover_jpgs = []
|
|
# in the directory containing songs find jpg and pngs containing string "cover"
|
|
for dir in episode_playlist:
|
|
path = pathlib.Path(dir).parent
|
|
for file in os.listdir(path):
|
|
for p in [".png", ".jpg", ".jpeg"]:
|
|
if p in file:
|
|
for i in ["cover", "COVER"]:
|
|
if i in file:
|
|
show_cover_jpgs.append(str(path) + "/" + file)
|
|
|
|
print(''''\n ------------------------
|
|
creating show cover art
|
|
------------------------ ''')
|
|
|
|
# if len(show_cover_jpgs) > 0: # duplicate this for variations of geometry
|
|
for i in range(variants):
|
|
x = show_cover_jpgs[:12]
|
|
combine_images(columns=4, space=10, images=random.sample(x,len(x)),variants=i)
|
|
return show_cover
|
|
|
|
def create_animated_gif():
|
|
import contextlib
|
|
from PIL import Image
|
|
|
|
# filepaths
|
|
fp_in = "/home/rob/antena/html/episode/2/img/*.png".format(episode_number)
|
|
fp_out = "/home/rob/antena/html/episode/2/img/show_cover.gif"
|
|
|
|
# use exit stack to automatically close opened images
|
|
with contextlib.ExitStack() as stack:
|
|
|
|
# lazily load images
|
|
imgs = (stack.enter_context(Image.open(f))
|
|
for f in sorted(glob.glob(fp_in)))
|
|
|
|
# extract first image from iterator
|
|
img = next(imgs)
|
|
|
|
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#gif
|
|
img.save(fp=fp_out, format='GIF', append_images=imgs,
|
|
save_all=True, duration=200, loop=0)
|
|
|
|
def create_pls_file():
|
|
# write the selection as a playlist file
|
|
with open("shows/antena_playlist_" + episode_date + ".pls","w") as file:
|
|
file.writelines("\n".join(episode_playlist))
|
|
|
|
def create_podcast(episode_playlist: list):
|
|
|
|
print('''------------------------
|
|
creating show audio
|
|
------------------------''')
|
|
|
|
from glob import glob
|
|
from pydub import AudioSegment
|
|
|
|
playlist_songs = [AudioSegment.from_file(flac_file) for flac_file in episode_playlist]
|
|
print(playlist_songs)
|
|
show_intro = playlist_songs[0]
|
|
# first_song = playlist_songs[0].fade_in(0) # only fadein if used over show intro - currently not used
|
|
# intro_and_first = first_song.overlay(show_intro)
|
|
first_three_blurb = playlist_songs[0]
|
|
second_three_blurb = playlist_songs[0]
|
|
final_songs_blurb = playlist_songs[0]
|
|
final_show_outro = playlist_songs[0]
|
|
|
|
# next two commented lines are for if intro goes over first track or not
|
|
#playlist = intro_and_first
|
|
|
|
playlist = show_intro
|
|
# playlist = []
|
|
|
|
#for song in playlist_songs[2:3]:
|
|
for song in playlist_songs[1:4]: # first three songs (first added with intro)
|
|
# We don't want an abrupt stop at the end, so let's do a 1 second crossfades
|
|
playlist = playlist.append(song)
|
|
print(song)
|
|
print("first songs added")
|
|
|
|
# blurb about first three tracks
|
|
# playlist = playlist.append(first_three_blurb) # <--------------BLURB INSERT
|
|
|
|
for song in playlist_songs[4:7]: # second three songs
|
|
playlist = playlist.append(song)
|
|
print(song)
|
|
print("second songs added")
|
|
|
|
# playlist = playlist.append(second_three_blurb) # <--------------BLURB INSERT
|
|
|
|
for song in playlist_songs[7:10]: # second three song
|
|
#playlist = playlist.append(song, crossfade=(1 * 1000))
|
|
playlist = playlist.append(song)
|
|
print(song)
|
|
print("third songs added")
|
|
|
|
# playlist = playlist.append(final_songs_blurb) # <--------------BLURB INSERT
|
|
|
|
for song in playlist_songs[10:13]: # second three song
|
|
playlist = playlist.append(song)
|
|
print(song)
|
|
print("final songs added")
|
|
|
|
# playlist = playlist.append(final_show_outro) # <-------------- OUTRO SEQUENCE
|
|
|
|
# get length of final show / podcast
|
|
playlist_length = len(playlist) / (1000*60)
|
|
|
|
# save the entire poidcast as FLAC
|
|
# with open("html/episode/{0}/show.flac".format(episode_number), 'wb') as out_f:
|
|
# print("FLAC output file opened...writing to file...")
|
|
# playlist.export(out_f, format='flac')
|
|
# print("FLAC audio file exported...")
|
|
|
|
# save the entire poidcast as MP3
|
|
with open("html/episode/{0}/show.mp3".format(episode_number), 'wb') as out_f:
|
|
print("MP3 output file opened...writing to file...")
|
|
playlist.export(out_f, format='mp3')
|
|
print("MP3 audio file exported...")
|
|
|
|
### ------------------------------------------------------------
|
|
def create_html_homepage_from_template(episode_playlist):
|
|
# TODO "on this weeks show" varients fed to html from list here
|
|
from jinja2 import Template, Environment, FileSystemLoader
|
|
|
|
env = Environment(loader=FileSystemLoader('html/templates'))
|
|
homepage_template = env.get_template('homepage.jinja')
|
|
|
|
conn = sqlite3.connect("database/show.db")
|
|
cursor = conn.cursor()
|
|
|
|
show_info = []
|
|
|
|
episode_artists = []
|
|
for i in range(10):
|
|
artists = []
|
|
cursor.execute("SELECT artist FROM EPISODES WHERE episode=?", [i])
|
|
rows = cursor.fetchall()
|
|
|
|
for artist in rows:
|
|
art = string=re.sub("\(.*?\)","", artist[0])
|
|
# shorten verbose artist names such as trojnik Trojnik (Cene Resnik, Tomaž Grom, Vid Drašler)
|
|
artist = string=re.sub("and","&",art)
|
|
artists.append(artist)
|
|
episode_artists.append(artists)
|
|
|
|
episodes = []
|
|
for i in range(10): # get this from new table EPISODE_STATS number of tracks in DB
|
|
print(episode_artists[i])
|
|
an_episode = dict(date="2012-02-", \
|
|
episode_artists=episode_artists[i], episode_number=i, \
|
|
episode_date=episode_date, \
|
|
episode_duration=episode_duration)
|
|
episodes.append(an_episode)
|
|
episodes = reversed(episodes) # reversed order to most recent episode appears first in list
|
|
|
|
cursor = conn.cursor()
|
|
cursor.execute('SELECT * FROM EPISODES WHERE episode=?', [episode_number])
|
|
r = cursor.fetchall()
|
|
|
|
for t in r:
|
|
song = str(t[0])
|
|
#track_label = str(t[1])
|
|
track_album = str(t[2])
|
|
track_title = str(t[3])
|
|
track_artist = str(t[4])
|
|
track_duration = float(t[6])
|
|
track_genre = str(t[7])
|
|
track_year = str(t[8])
|
|
|
|
detail = str(track_artist) + " | " + str(track_album) + \
|
|
" | " + str(track_title) + " | " + str(track_year) + \
|
|
" | " + str(timedelta(seconds=round(track_duration)))
|
|
show_info.append("" + detail)
|
|
|
|
output_from_parsed_template = homepage_template.render(\
|
|
show_name=''.join(random.choice((str.upper,str.lower))(x) for x in show_name), \
|
|
episodes=episodes, episode_author="Rob Canning",\
|
|
episode_duration=episode_duration, episode_number=episode_number, \
|
|
episode_artists=episode_artists[episode_number], \
|
|
about_show=show_short_description, episode_playlist=show_info, \
|
|
episode_image="episode/{0}/img/cover.png".format(episode_number))
|
|
|
|
with open("html/index.html".format(episode_number), "w") as episode_page:
|
|
episode_page.write(output_from_parsed_template)
|
|
|
|
|
|
def create_html_episode_from_template(episode_playlist):
|
|
|
|
from jinja2 import Template, Environment, FileSystemLoader
|
|
|
|
env = Environment(loader=FileSystemLoader('html/templates'))
|
|
episode_template = env.get_template('episode.jinja')
|
|
|
|
show_info = []
|
|
|
|
# maybe a jinja2 template loop here instead
|
|
cursor = conn.cursor()
|
|
|
|
e = Table('EPISODES')
|
|
q = Query.from_(e).select(
|
|
e.episode
|
|
).where(
|
|
e.episode == episode_number)
|
|
# raw 'SELECT * FROM EPISODES WHERE EPISODE=?', [episode_number]
|
|
cursor.execute('SELECT * FROM EPISODES WHERE EPISODE=?', [episode_number])
|
|
|
|
r = cursor.fetchall()
|
|
for t in r:
|
|
song = str(t[0])
|
|
#track_label = str(t[1])
|
|
track_album = str(t[3])
|
|
track_title = str(t[4])
|
|
track_artist = str(t[5]).upper()
|
|
track_duration = float(t[6])
|
|
track_genre = str(t[7])
|
|
track_year = str(t[8])
|
|
|
|
detail = str(track_artist) + " | " + str(track_album) + \
|
|
" | " + str(track_title) + " | " + str(track_year) + \
|
|
" | " + str(timedelta(seconds=round(track_duration)))
|
|
show_info.append("" + detail)
|
|
|
|
#TODO FIX THIS UP TO SEND COLUMNS FROM LIST TO JINJA
|
|
|
|
output_from_parsed_template = episode_template.render(\
|
|
show_name=show_name, episode_author="Rob Canning",\
|
|
episode_duration=episode_duration, \
|
|
episode_date=episode_date, about_show=show_short_description, \
|
|
episode_playlist=show_info, \
|
|
episode_image="img/cover.png".format(episode_number))
|
|
|
|
with open("html/episode/{0}/index.html".format(episode_number), "w") as episode_page:
|
|
episode_page.write(output_from_parsed_template)
|
|
|
|
|
|
def create_RSS_XML_from_template():
|
|
|
|
from jinja2 import Template, Environment, FileSystemLoader
|
|
|
|
env = Environment(loader=FileSystemLoader('html/templates'))
|
|
rss_template = env.get_template('show_RSS.jinja.xml')
|
|
output_from_parsed_template = rss_template.render(\
|
|
show_name=show_name, \
|
|
episode_number=int(episode_number), episode_author="Rob Canning",\
|
|
episode_duration=episode_duration, about_show=show_short_description, \
|
|
episode_image="img/cover.png".format(episode_number))
|
|
with open("html/show_rss.xml".format(episode_number), "w") as rss_page:
|
|
rss_page.write(output_from_parsed_template)
|
|
|
|
|
|
|
|
def main():
|
|
|
|
database_create_episodes_table()
|
|
set_episode_date(input_date)
|
|
create_episode_playlist(episode_playlist, complete_playlist)
|
|
create_show_coverart(episode_playlist, 1) #episode_duration = 100
|
|
#create_animated_gif()
|
|
create_intro(episode_playlist)
|
|
create_pls_file()
|
|
create_html_episode_from_template(episode_playlist)
|
|
create_html_homepage_from_template(episode_playlist)
|
|
create_RSS_XML_from_template()
|
|
conn.close()
|
|
create_podcast(episode_playlist)
|
|
|
|
|
|
|
|
|
|
|
|
main()
|
|
|
|
|
|
|
|
|
|
|
|
#convert -delay 100 -loop 0 html/episode/2/img/show_cover_2024-01-12* animatedGIF.gif
|