diff --git a/audio/texts/texts.md b/audio/texts/texts.md index 700de62..0dd5859 100644 --- a/audio/texts/texts.md +++ b/audio/texts/texts.md @@ -2,7 +2,11 @@ ## intros -- welcome to domaca juha, a weekly selection of some of the most interesting and innovative contemporary slovenian independent music +- welcome to uho!, +a eclectic selection of some of the most interesting +and innovative contemporary slovenian independent music +brought to you each week by me, your host, rob canning. + - you are listening to domaca juha - 12 tracks every week exploring the slovenian independent music scene diff --git a/database/show.db b/database/show.db index 8f297d5..1344540 100644 Binary files a/database/show.db and b/database/show.db differ diff --git a/mk_show.py b/mk_show.py index c2b925d..5d03ce6 100755 --- a/mk_show.py +++ b/mk_show.py @@ -2,25 +2,21 @@ # CREATE UHO EPISODE ----------------------------------------- -import sys, os, datetime, fnmatch, glob, random, time, pathlib, re +import sys, os, datetime, fnmatch, glob, random, time, pathlib, re,\ +sqlite3, json, subprocess, uuid, argparse, contextlib +from pydub import AudioSegment from datetime import timedelta from os.path import join from tinytag import TinyTag from random import shuffle -import sqlite3, json -import subprocess, uuid, argparse, contextlib from pypika import Query, Table, Field, Column from PIL import Image, ImageDraw, ImageFont from jinja2 import Template, Environment, FileSystemLoader +from glob import glob import tkinter as tk from tkinter import filedialog as fd -# TODO get specific file into playlist -#name= fd.askopenfilename() -#print(name) -# look up the path in db to get full details to add to episode - parser = argparse.ArgumentParser() parser.add_argument("-d", "--date", help="Show Date") parser.add_argument("-e", "--episode", help="Episode Number", type=int) @@ -33,7 +29,8 @@ parser.add_argument("-t", "--top_tail", action="store_true") args = parser.parse_args() -path = "/home/rob/uho/" +path = pathlib.Path.cwd() + show_name = "UhO" show_url = "https://uho.rizom.si" show_rss = "podcast_rss.xml" @@ -42,6 +39,8 @@ episode_author="Rob Canning" show_short_description = '''The UhO! podcast presents an eclectic selection of independent music from Slovenia. The show aims to deliver as broad a range of genres as possible; banging techno, sludge, math rock, contemporary classical, doom, free improvisation, noise music, glitch, jazz skronk, field recordings, ambient, drone....etc etc... whatever the genre, you can be sure you are listening to the latest and most inovative music on offer in this part of the world. Hosted and compiled by Rob Canning, the show is published weekly by Zavod Rizoma and is broadcast in Slovenia on FM by mariborski radio študent: MARŠ. It as also available as a podcast. Use our RSS feed or search for UhO Podcast where ever you subscribe to podcasts''' + + episode_number = args.episode input_date = args.date episode_date = datetime.datetime.now().strftime("%Y-%m-%d") @@ -54,7 +53,7 @@ artist_abreviated = [] # sqlite database connection conn = sqlite3.connect("database/show.db") -web_path = "/home/rob/uho/html/episode/{0}/img".format(episode_number) +web_path = "{0}/html/episode/{1}/img".format(path, episode_number) if not os.path.exists(web_path): os.makedirs(web_path) @@ -69,7 +68,7 @@ def set_episode_date(input_date): def get_intro(): - intropath = path + "audio/intros" + intropath = "audio/intros" intro = random.choice(os.listdir(intropath)) #final_playlist.insert(0, str(os.path.abspath(intropath)) + "/" + str(intro)) return str(str(intropath) + "/" + str(intro)) @@ -78,7 +77,6 @@ def choose_a_track(conn, episode_number, ): print("adding a track") global episode_duration global track_count - # global archive set_episode_date(input_date) @@ -89,7 +87,11 @@ def choose_a_track(conn, episode_number, ): cursor.execute("SELECT * FROM MUSIC_LIBRARY ORDER BY RANDOM() LIMIT 1 ;") r = cursor.fetchone() # FETCH ONE RANDOM TRACK FROM THE DATABASE print(r) - # for t in r: + + # TODO lookup path and get complete db record + #name= fd.askopenfilename() + #print(name) + song = str(r[9]) track_label = str(r[1]) track_album = str(r[2]) @@ -135,6 +137,8 @@ def choose_a_track(conn, episode_number, ): return '{:0>2}'.format(track_count) +# TODO figure out logging to see what is getting rejected etc. + # else: print("TRACK TOO SHORT..........." ) # else: print("TRACK TOO LONG..........." ) # else: print("SONG PLAYED IN PREVIOUS EPISODE" ) @@ -212,7 +216,7 @@ def create_episode_playlist(conn, episode_number): global track_count track_count = 1 - max_track_dur = 10 + max_track_dur = 14 min_track_dur = 2 episode_duration = 0 @@ -223,7 +227,8 @@ def create_episode_playlist(conn, episode_number): cursor.execute('DELETE FROM EPISODES WHERE episode = {0}'.format(episode_number)) # TODO what is most important 12 tracks or 60 minutes - while episode_duration < 60 * 60 and track_count <= 12 : + # 56 mins of audio rest for fillers - aprox. + while episode_duration < 56 * 60 and track_count <= 12 : print(track_count) choose_a_track(conn, episode_number) @@ -261,8 +266,11 @@ def playlist_add_track(conn, episode_number, episode_duration): # this is where the index number comes from in the below choose_a_track function #track_count = track_to_add # add the new track + + # TODO specific track via gui or random choice or random from gui selected directory + # choose a random choose_a_track(conn, episode_number) - # conn.commit() + # re sequence track numbers cursor.execute('\ UPDATE EPISODES SET track_number = (SELECT COUNT(*)+1 FROM EPISODES r \ @@ -290,10 +298,8 @@ def playlist_delete_track(conn, episode_number, episode_duration): cursor.execute('\ UPDATE EPISODES SET track_number = (SELECT COUNT(*)+1 FROM EPISODES r \ WHERE EPISODES.track_number>r.track_number AND EPISODE=?);',[episode_number] ) - conn.commit() - # get the new episode time cursor.execute('SELECT SUM(trackdur) FROM EPISODES WHERE EPISODE=? ', [episode_number]) episode_duration = cursor.fetchone()[0] @@ -341,7 +347,7 @@ def playlist_preview_track(conn, episode_number, episode_duration): cursor.execute('SELECT path FROM EPISODES WHERE EPISODE=? AND track_number=?', [episode_number, preview_track ]) preview_track_path = cursor.fetchone()[0] print(preview_track_path) - subprocess.Popen(["nohup vlc '{0}' &".format(preview_track_path)], shell=True) + subprocess.Popen(["vlc '{0}' &".format(preview_track_path)], shell=True) modify_playlist(conn, episode_number, episode_duration) @@ -361,7 +367,8 @@ def modify_playlist(conn, episode_number, episode_duration): for i in preview: print('| {0} | {3} ||| {2} ||| {1} ||| {5} ||| [{6}] ||| {4} |||\n'\ - .format('{:0>2}'.format(i[2]), i[4], i[5], i[6], timedelta(seconds=round(i[7])), i[9], i[11] ) ) + .format('{:0>2}'.format(i[2]), i[4], i[5], i[6],\ + timedelta(seconds=round(i[7])), i[9], i[11] ) ) print(">> SELECT AN OPTION: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>") @@ -489,8 +496,6 @@ def combine_images(columns, space, images, variants:int): # ------------------------------------------------------------------------------- - - def create_show_coverart(variants): set_episode_date(input_date) @@ -526,8 +531,6 @@ def create_show_coverart(variants): return show_cover def create_animated_gif(): - import contextlib - # filepaths fp_in = "/home/rob/uho/html/episode/2/img/*.png".format(episode_number) @@ -556,11 +559,16 @@ def create_podcast(): print('''------------------------ creating show audio ------------------------''') + archive_path = "archive/e".format(episode_number) - from glob import glob - from pydub import AudioSegment + if not os.path.exists(archive_path): + os.makedirs(archive_path) - fill_path = "audio/fills/" + from distutils.dir_util import copy_tree + copy_tree("archive/blank_fills/", "archive/e/{0}/audio_fills".format(episode_number)) + + + fill_path = "{0}/{1}/audio_fills".format(archive_path, episode_number) # grab the conpleted playlist from the database final_playlist = [] cursor = conn.cursor() @@ -572,20 +580,19 @@ def create_podcast(): for i in preview: # database >> python list # if its the first track then put the show intro before it # no track intro but yes track outro - #episode_number=0 #TODO undo this ... just for testing if i[2] == 1: - print("adding track intro: " + "{0}e_{1}_t_{2}_in.flac".format(fill_path, episode_number, i[2] )) + print("adding track intro: " + "{0}/{2}_in.flac".format(fill_path, episode_number, i[2] )) intro = get_intro() - final_playlist.insert(0,intro) + final_playlist.insert(0,intro) # insert the intro print("adding track: " + i[10]) - final_playlist.insert(1, i[10]) # insert the intro - print("adding track intro: " + "{0}e_{1}_t_{2}_out.flac".format(fill_path, episode_number, i[2] )) - final_playlist.append("{0}e_{1}_t_{2}_out.flac".format(fill_path, episode_number, i[2] )) - elif i[2] == len(preview): + final_playlist.insert(1, i[10]) # insert the first track + print("adding track outro: " + "{0}/{2}_out.flac".format(fill_path, episode_number, i[2] )) + final_playlist.append("{0}/{2}_out.flac".format(fill_path, episode_number, i[2] )) - print("adding track intro: " + "{0}e_{1}_t_{2}_in.flac".format(fill_path, episode_number, i[2] )) - final_playlist.append("{0}e_{1}_t_{2}_in.flac".format(fill_path, episode_number, i[2] )) - print("adding last track intro no outro : " + i[10]) + elif i[2] == len(preview): # if it's the last track then dont have an outro + print("adding track intro: " + "{0}/{2}_in.flac".format(fill_path, episode_number, i[2] )) + final_playlist.append("{0}/{2}_in.flac".format(fill_path, episode_number, i[2] )) + print("adding last track intro no outro - the show ends with music : " + i[10]) final_playlist.append(i[10]) else: @@ -593,12 +600,12 @@ def create_podcast(): # x 2 # TODO = episode_number must be zero to get the dummy filler files - - print("adding track intro: " + "{0}e_{1}_t_{2}_in.flac".format(fill_path, episode_number, i[2] )) - final_playlist.append("{0}e_{1}_t_{2}_in.flac".format(fill_path, episode_number, i[2] )) + print("adding track intro: " + "{0}/{2}_in.flac".format(fill_path, episode_number, i[2] )) + final_playlist.append("{0}/{2}_in.flac".format(fill_path, episode_number, i[2] )) print("adding track: " + i[10]) final_playlist.append(i[10]) - print("adding track outro: " + "{0}e_{1}_t_{2}_out.flac".format(fill_path, episode_number, i[2] )) - final_playlist.append("{0}e_{1}_t_{2}_out.flac".format(fill_path, episode_number, i[2] )) + print("adding track outro: " + "{0}/{2}_out.flac".format(fill_path, episode_number, i[2] )) + final_playlist.append("{0}/{2}_out.flac".format(fill_path, episode_number, i[2] )) print(''' ======================================================================== @@ -612,8 +619,16 @@ def create_podcast(): # makes an array of songs and fills as audiosegments show_playlist = [AudioSegment.from_file(flac_file) for flac_file in final_playlist] - playlist = show_playlist[0] - for track in show_playlist[1:]: + #playlist = show_playlist[0] # insert intro at head of playlist + + track1 = show_playlist[1].fade_in(18000) # 18 second fade in to track 1 + playlist = track1.overlay(show_playlist[0],position= (len(track1)*-1)+50, gain_during_overlay=0) + + #rest = show_playlist[1][len(show_playlist[0]):] + #playlist = playlist.append(rest) + + #playlist = playlist.append(show_playlist[1], crossfade=2000) # crossfade first track over intro + for track in show_playlist[2:]: # add the rest of the tracks in playlist playlist = playlist.append(track) # save the entire poidcast as FLAC @@ -625,7 +640,7 @@ def create_podcast(): # 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') + playlist.export(out_f, format='mp3', tags={'title': str('Uho! Episode #{0} '.format(episode_number)), 'artist': 'https://uho.rizom.si', 'album': 'THE UHO PODCAST', 'comments': 'https://uho.rizom.si'}) print("MP3 audio file exported...") # TODO duration of final file @@ -636,7 +651,6 @@ def create_podcast(): def create_html_homepage_from_template(): # TODO "on this weeks show" variants fed to html from list here - from jinja2 import Template, Environment, FileSystemLoader set_episode_date(input_date) @@ -717,9 +731,6 @@ def parse_db_episode(): env = Environment(loader=FileSystemLoader('html/templates')) episode_template = env.get_template('episode.jinja') - - - # maybe a jinja2 template loop here instead cursor = conn.cursor() e = Table('episodes') @@ -818,8 +829,6 @@ def create_html_episode_from_template(episode_number, episode_duration): def create_RSS_XML_from_template(): - from jinja2 import Template, Environment, FileSystemLoader - set_episode_date(input_date) env = Environment(loader=FileSystemLoader('html/templates')) @@ -842,7 +851,6 @@ def main(): if args.playlist == "new": print("creating new playlist >>>>> database ") - create_episode_playlist(conn, episode_number) elif args.playlist=="keep": @@ -871,7 +879,7 @@ def main(): if args.art == True: print("creating artwork for socials ......") - create_show_coverart(4) #episode_duration = 100 + create_show_coverart(4) #create_animated_gif() if args.mp3 == True: diff --git a/recorder.py b/recorder.py new file mode 100755 index 0000000..bf01d90 --- /dev/null +++ b/recorder.py @@ -0,0 +1,270 @@ +#!/usr/bin/python3 + +from pynput import keyboard +import time, os +import pyaudio +import wave +import sched +import sys +from playsound import playsound + +from threading import Thread, Lock +from pynput import keyboard +import pyaudio +import wave + + +import sys, os, datetime, fnmatch, glob, random, time, pathlib, re,\ +sqlite3, json, subprocess, uuid, argparse, contextlib +from pydub import AudioSegment +from datetime import timedelta +from os.path import join +from tinytag import TinyTag +from random import shuffle +from pypika import Query, Table, Field, Column +from PIL import Image, ImageDraw, ImageFont +from jinja2 import Template, Environment, FileSystemLoader +from glob import glob + +import tkinter as tk +from tkinter import filedialog as fd + +parser = argparse.ArgumentParser() +parser.add_argument("-d", "--date", help="Show Date") +parser.add_argument("-e", "--episode", help="Episode Number", type=int) +parser.add_argument("-p", "--playlist", help="new, keep or edit", default="keep") +parser.add_argument("-m", "--mp3", action="store_true") +parser.add_argument("-w", "--web", action="store_true") +parser.add_argument("-a", "--art", action="store_true") +parser.add_argument("-i", "--insert_fills", action="store_true") +parser.add_argument("-t", "--top_tail", action="store_true") + +args = parser.parse_args() + +path = pathlib.Path.cwd() + +show_name = "UhO" +show_url = "https://uho.rizom.si" +show_rss = "podcast_rss.xml" +show_email = "uho.podcast@gmail.com" +episode_author="Rob Canning" + +show_short_description = '''The UhO! podcast presents an eclectic selection of independent music from Slovenia. The show aims to deliver as broad a range of genres as possible; banging techno, sludge, math rock, contemporary classical, doom, free improvisation, noise music, glitch, jazz skronk, field recordings, ambient, drone....etc etc... whatever the genre, you can be sure you are listening to the latest and most inovative music on offer in this part of the world. Hosted and compiled by Rob Canning, the show is published weekly by Zavod Rizoma and is broadcast in Slovenia on FM by mariborski radio študent: MARŠ. It as also available as a podcast. Use our RSS feed or search for UhO Podcast where ever you subscribe to podcasts''' + + + +episode_number = args.episode +input_date = args.date +episode_date = datetime.datetime.now().strftime("%Y-%m-%d") +episode_duration = 10 +show_cover = "" +archive = [] +#artists_played = [] +artist_abreviated = [] + +# sqlite database connection +conn = sqlite3.connect("database/show.db") + +fill_path = "archive/e/{0}/audio_fills".format(episode_number) +if not os.path.exists(fill_path): + os.makedirs(fill_path) + +#TODO lookup metadata from music_db not from episodoe = use uuids! +#TODO scrape artist bio from bandcamp? +#TODO GET VARIATION PROMPTS + +def get_playlist_for_fill_recording(conn, episode_number, episode_duration): + cursor = conn.cursor() + cursor.execute('SELECT * FROM EPISODES WHERE EPISODE=? ORDER BY track_number ASC', [episode_number]) + preview = cursor.fetchall() + cursor.execute('SELECT SUM(trackdur) FROM EPISODES WHERE EPISODE=? ', [episode_number]) + episode_duration = cursor.fetchone()[0] + + os.system("clear") + print("\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>") + print(">> PROPOSED EPISODE #{0} PLAYLIST: ({1}) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"\ + .format(episode_number, timedelta(seconds=round(episode_duration)))) + print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n") + + for i in preview: + print('| {0} | {3} ||| {2} ||| {1} ||| {5} ||| [{6}] ||| {4} |||\n'\ + .format('{:0>2}'.format(i[2]), i[4], i[5], i[6],\ + timedelta(seconds=round(i[7])), i[9], i[11] ) ) + + print(">> SELECT AN OPTION: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>") + + user_input = input(''' + (l)isten to track + R ECORD OUTRO FILL + +>>>>>>>>>>>>>>>>>>>>>>>>>>> OR PRESS ENTER TO PROCEED................... : ''') + + if user_input == 'l': + playlist_preview_track(conn, episode_number, episode_duration) + + elif user_input == "r": + num = input("Which TRACK # to record outro fill for : ") + the_file = "archive/e/{0}/audio_fills/{1}_out.wav".format(episode_number, num) + print(the_file) + r = recorder(the_file) + p = player(the_file) + l = listener(r, p) + os.system("clear") + + for i in preview[int(num)-1:int(num)]: + print(''' + + POD TRACK#: {0} + TRACK: {2} + ARTIST: {3} + ALBUM: {1} + YEAR: {5} + LABEL: [{6}] + DURATION: {4} + GENRE: {8} + PATH: + {9} + COMMENT: {7} + '''\ + .format('{:0>2}'.format(i[2]), i[4], i[5], i[6],\ + timedelta(seconds=round(i[7])), i[9], i[11], i[12], i[8], i[10] ) ) + + + print('............................Hold ctrl to record, press p to playback, press q to quit') + l.start() #keyboard listener is a thread so we start it here + l.join() #wait for the tread to terminate so the program doesn't instantly close + + # todo implement a check box TUI showing track fills completed + + get_playlist_for_fill_recording(conn, episode_number, 10) + + + + + else: + get_playlist_for_fill_recording(conn, episode_number, 10) + + + + + + +class player: + def __init__(self, wavfile): + self.wavfile = wavfile + self.playing = 0 #flag so we don't try to record while the wav file is in use + self.lock = Lock() #muutex so incrementing and decrementing self.playing is safe + + #contents of the run function are processed in another thread so we use the blocking + # version of pyaudio play file example: http://people.csail.mit.edu/hubert/pyaudio/#play-wave-example + def run(self): + with self.lock: + self.playing += 1 + with wave.open(self.wavfile, 'rb') as wf: + p = pyaudio.PyAudio() + stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), + channels=wf.getnchannels(), + rate=wf.getframerate(), + output=True) + data = wf.readframes(8192) + while data != b'': + stream.write(data) + data = wf.readframes(8192) + + stream.stop_stream() + stream.close() + p.terminate() + wf.close() + with self.lock: + self.playing -= 1 + + def start(self): + Thread(target=self.run).start() + + +class recorder: + def __init__(self, + wavfile, + chunksize=8192, + dataformat=pyaudio.paInt16, + channels=1, + rate=44100): + self.filename = wavfile + self.chunksize = chunksize + self.dataformat = dataformat + self.channels = channels + self.rate = rate + self.recording = False + self.pa = pyaudio.PyAudio() + + def start(self): + #we call start and stop from the keyboard listener, so we use the asynchronous + # version of pyaudio streaming. The keyboard listener must regain control to + # begin listening again for the key release. + if not self.recording: + self.wf = wave.open(self.filename, 'wb') + self.wf.setnchannels(self.channels) + self.wf.setsampwidth(self.pa.get_sample_size(self.dataformat)) + self.wf.setframerate(self.rate) + + def callback(in_data, frame_count, time_info, status): + #file write should be able to keep up with audio data stream (about 1378 Kbps) + self.wf.writeframes(in_data) + return (in_data, pyaudio.paContinue) + + self.stream = self.pa.open(format = self.dataformat, + channels = self.channels, + rate = self.rate, + input = True, + stream_callback = callback) + self.stream.start_stream() + self.recording = True + print('recording started') + + def stop(self): + if self.recording: + self.stream.stop_stream() + self.stream.close() + self.wf.close() + + self.recording = False + print('recording finished') + +class listener(keyboard.Listener): + + def __init__(self, recorder, player): + super().__init__(on_press = self.on_press, on_release = self.on_release) + self.recorder = recorder + self.player = player + + def on_press(self, key): + if key is None: #unknown event + pass + elif isinstance(key, keyboard.Key): #special key event + if key.ctrl and self.player.playing == 0: + self.recorder.start() + elif isinstance(key, keyboard.KeyCode): #alphanumeric key event + if key.char == 'q': #press q to quit + if self.recorder.recording: + self.recorder.stop() + return False #this is how you stop the listener thread + if key.char == 'p' and not self.recorder.recording: + self.player.start() + + def on_release(self, key): + if key is None: #unknown event + pass + elif isinstance(key, keyboard.Key): #special key event + if key.ctrl: + self.recorder.stop() + elif isinstance(key, keyboard.KeyCode): #alphanumeric key event + pass + + + +if __name__ == '__main__': + get_playlist_for_fill_recording(conn, episode_number, 10) + + + +