271 lines
9.7 KiB
Python
271 lines
9.7 KiB
Python
|
#!/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)
|
||
|
|
||
|
|
||
|
|
||
|
|