initial commit
This commit is contained in:
commit
6426699056
65
blank.schema.sql
Normal file
65
blank.schema.sql
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
-- MySQL dump 10.16 Distrib 10.1.21-MariaDB, for Linux (x86_64)
|
||||||
|
--
|
||||||
|
-- Host: db.domain.tld Database: myDB
|
||||||
|
-- ------------------------------------------------------
|
||||||
|
-- Server version 10.1.21-MariaDB
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8 */;
|
||||||
|
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||||
|
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||||
|
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `myTBL`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `myTBL`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `myTBL` (
|
||||||
|
`episode` varchar(8) NOT NULL,
|
||||||
|
`file_prefix` varchar(255) NOT NULL,
|
||||||
|
`sha_mp3` char(64) NOT NULL,
|
||||||
|
`sha_ogg` char(64) NOT NULL,
|
||||||
|
`bytesize_mp3` int(16) NOT NULL,
|
||||||
|
`bytesize_ogg` int(16) NOT NULL,
|
||||||
|
`length` int(8) NOT NULL,
|
||||||
|
`editor` varchar(64) NOT NULL,
|
||||||
|
`intro_title` varchar(128) NOT NULL,
|
||||||
|
`intro_artist` varchar(128) NOT NULL,
|
||||||
|
`intro_link` varchar(256) NOT NULL,
|
||||||
|
`intro_copyright` varchar(45) NOT NULL,
|
||||||
|
`intro_copyrightlink` varchar(256) NOT NULL,
|
||||||
|
`outro_title` varchar(128) NOT NULL,
|
||||||
|
`outro_artist` varchar(128) NOT NULL,
|
||||||
|
`outro_link` varchar(256) NOT NULL,
|
||||||
|
`outro_copyright` varchar(45) NOT NULL,
|
||||||
|
`outro_copyrightlink` varchar(256) NOT NULL,
|
||||||
|
`recorded` datetime NOT NULL,
|
||||||
|
`released` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`episode`),
|
||||||
|
UNIQUE KEY `episode_UNIQUE` (`episode`),
|
||||||
|
UNIQUE KEY `file_prefix_UNIQUE` (`file_prefix`),
|
||||||
|
UNIQUE KEY `sha_mp3_UNIQUE` (`sha_mp3`),
|
||||||
|
UNIQUE KEY `sha_ogg_UNIQUE` (`sha_ogg`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
|
-- Dump completed on 2017-03-23 18:19:43
|
153
podloader.ini.dist
Normal file
153
podloader.ini.dist
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
## Podloader config ##
|
||||||
|
|
||||||
|
[rsync]
|
||||||
|
# The server the files go to
|
||||||
|
host = domain.tld
|
||||||
|
|
||||||
|
# The remote path root. This must be a full/absolute path!
|
||||||
|
path = /srv/http/myVhostDir
|
||||||
|
|
||||||
|
# The remote user
|
||||||
|
user = sshuser
|
||||||
|
|
||||||
|
[mysql]
|
||||||
|
# The mysql server. Note that this will be overridden if you
|
||||||
|
# use a .my.cnf and a host is specified in there.
|
||||||
|
host = db.${rsync:host}
|
||||||
|
|
||||||
|
# The mysql server's port. Note that this will be overridden
|
||||||
|
# if you use a .my.cnf and a port is specified in there.
|
||||||
|
port = 3306
|
||||||
|
|
||||||
|
# The mysql user. Note that this will be overridden if you
|
||||||
|
# use a .my.cnf and a user is specified in there.
|
||||||
|
user = mysqluser
|
||||||
|
|
||||||
|
# The mysql DB. Note that this will be overridden if you
|
||||||
|
# use a .my.cnf and a user is specified in there.
|
||||||
|
db = myDB
|
||||||
|
|
||||||
|
# The mysql table
|
||||||
|
table = myTBL
|
||||||
|
|
||||||
|
# The column names (separated by commas) for, in order:
|
||||||
|
# episode ID (e.g. "S1E2")
|
||||||
|
# file_prefix (the filename only WITHOUT .ogg/.mp3)
|
||||||
|
# sha_mp3 (the column to hold the SHA256 of the MP3 file)
|
||||||
|
# sha_ogg (the column to hold the SHA256 of the OGG file)
|
||||||
|
# bytesize_mp3 (size of the MP3 file in bytes)
|
||||||
|
# bytesize_ogg (size of the OGG file in bytes)
|
||||||
|
# length (the length of the track in seconds)
|
||||||
|
# editor (the name of the person that edited the audio track(s)
|
||||||
|
# intro_title (the title of the intro music track)
|
||||||
|
# intro_artist (the artist that composed the intro music track)
|
||||||
|
# intro_link (a URL to the intro track or artist's site/page)
|
||||||
|
# intro_copyright (the copyright license for the intro track, e.g. "CC-BY-SA 3.0")
|
||||||
|
# intro_copyrightlink (a URL to the full terms of the intro track's copyright)
|
||||||
|
# outro_title (the title of the outro music track)
|
||||||
|
# outro_artist (the artist that composed the outro music track)
|
||||||
|
# outro_link (a URL to the outro track or artist's site/page)
|
||||||
|
# outro_copyright (the copyright license for the outro track, e.g. "CC-BY-SA 3.0")
|
||||||
|
# outro_copyrightlink (a URL to the full terms of the outro track's copyright)
|
||||||
|
# recorded (when the episode was recorded)
|
||||||
|
# released (when the episode was released)
|
||||||
|
#
|
||||||
|
# Note that a dump of the *table* is included (blank.schema.sql). Feel free to use it:
|
||||||
|
# mysql -e "CREATE DATABASE myDB" && mysql myDB < blank.schema.sql
|
||||||
|
# This will create a database named "myDB" (you can skip that part if you already have a database),
|
||||||
|
# and create a table named "myTBL" according to the default spec outlined in here.
|
||||||
|
#
|
||||||
|
cols = episode,file_prefix,sha_mp3,sha_ogg,bytesize_mp3,bytesize_ogg,length,editor,intro_title,intro_artist,intro_link,intro_copyright,intro_copyrightlink,outro_title,outro_artist,outro_link,outro_copyright,outro_copyrightlink,recorded,released
|
||||||
|
|
||||||
|
# The remote mysql password - if this is set to False/no/0,
|
||||||
|
# we'll just use the my.cnf-formatted INI file (e.g. ~/.my.cnf) instead.
|
||||||
|
password = False
|
||||||
|
|
||||||
|
# If the above is False, path to the .my.cnf
|
||||||
|
conf = ~/.my.cnf
|
||||||
|
|
||||||
|
# If password is False, what [client] section suffix should we use?
|
||||||
|
# Note that this is going to look like e.g. [clientremote1] in the config
|
||||||
|
# file. (correlates to mysql's --defaults-group-suffix=)
|
||||||
|
confsec = remote1
|
||||||
|
|
||||||
|
[gpg]
|
||||||
|
# Should we actually sign episodes? True/yes/1 or False/no/0.
|
||||||
|
enabled = True
|
||||||
|
|
||||||
|
# The GPG key ID(s) (in a comma-separated list) to sign the episode with.
|
||||||
|
# You must have the private key in your *local* keyring!
|
||||||
|
keys = D34DB33FD34DB33FD34DB33FD34DB33FD34DB33F
|
||||||
|
|
||||||
|
# The path to your GNUPG homedir.
|
||||||
|
homedir = ~/.gnupg
|
||||||
|
|
||||||
|
[local]
|
||||||
|
# The local path root to the edited FLAC files
|
||||||
|
path = ~/podcast
|
||||||
|
|
||||||
|
# A subdir for the episode-specific files. If it contains one of the following values,
|
||||||
|
# substitution will be done.
|
||||||
|
# Special values:
|
||||||
|
# - SEASONEPISODE = A special string that uses the -s/--season and -e/--episode strings together.
|
||||||
|
# i.e. if season is 1 and episode is 13, it'd be "s1e13".
|
||||||
|
# - SEASON = A special string that uses -s/--season.
|
||||||
|
# - EPISODE = A special string that uses -e/--episode.
|
||||||
|
subdir = SEASONEPISODE
|
||||||
|
|
||||||
|
# Where the transcoded media and GPG sigs (if enabled) should go
|
||||||
|
# (in a structure of <path>/<season>/<episode>/{mp3,ogg,gpg}/)
|
||||||
|
mediadir = ${path}/releases
|
||||||
|
|
||||||
|
[tags]
|
||||||
|
# What should the Artist string be?
|
||||||
|
artist = Podcastin' Joe
|
||||||
|
|
||||||
|
# What should the Album name be?
|
||||||
|
# If you set this to SEASON, it will set this to whatever's specified for -s/--season
|
||||||
|
album = SEASON
|
||||||
|
|
||||||
|
# How many digits should the season be padded to? (i.e. the minimum number of digits)
|
||||||
|
# A pad of three would have Season 3 be "003".
|
||||||
|
season_pad = 1
|
||||||
|
|
||||||
|
# How many digits should the episode be padded to? (i.e. the minimum number of digits)
|
||||||
|
# A pad of three would have Episode 1 be "001".
|
||||||
|
episode_pad = 1
|
||||||
|
|
||||||
|
# What should the Year be set to?
|
||||||
|
# If set as False/no/0, it will be automatically determined by the raw media file's metadata.
|
||||||
|
year = False
|
||||||
|
|
||||||
|
# What track number should be set?
|
||||||
|
# If set as EPISODE, it will set this to whatever's specified for -e/--episode
|
||||||
|
track = EPISODE
|
||||||
|
|
||||||
|
# What genre should be set?
|
||||||
|
genre = Podcast
|
||||||
|
|
||||||
|
# What should be set as the comment field?
|
||||||
|
comment = https://podcast.domain.tld
|
||||||
|
|
||||||
|
# What should be set as the Copyright notice?
|
||||||
|
copyright = CC-BY-SA 4.0
|
||||||
|
|
||||||
|
# What should be set for the URL field?
|
||||||
|
# Special values:
|
||||||
|
# - SEASONEPISODE = A special string that uses the -s/--season and -e/--episode strings together.
|
||||||
|
# i.e. if season is 1 and episode is 13, it'd be "S1E13".
|
||||||
|
# - SEASON = A special string that uses -s/--season.
|
||||||
|
# - EPISODE = A special string that uses -e/--episode.
|
||||||
|
url = ${comment}/episodes/SEASONEPISODE
|
||||||
|
|
||||||
|
# Who encoded the file? (e.g. what is your name)
|
||||||
|
encoded = Joe Schmoe
|
||||||
|
|
||||||
|
# Who edited the episode? (see -d/--editor)
|
||||||
|
# Note that this can contain (and should, if available)
|
||||||
|
# contain a link (e.g.:
|
||||||
|
# <a href="https://editorname.tld">Editor Name</a> )
|
||||||
|
editor = <a href="${comment}/editor">Some Editor</a>
|
||||||
|
|
||||||
|
# A local path to the image to embed.
|
||||||
|
img = ${local:path}/images/podcast_logo.jpg
|
552
podloader.py
Executable file
552
podloader.py
Executable file
@ -0,0 +1,552 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
import subprocess
|
||||||
|
import hashlib
|
||||||
|
import datetime
|
||||||
|
import pymysql
|
||||||
|
import magic
|
||||||
|
import gpgme
|
||||||
|
from mutagen.id3 import ID3, APIC, TALB, TDRC, TENC, TRCK, COMM, WXXX, TCON, TIT2, TPE1, TCOP
|
||||||
|
from mutagen.oggvorbis import OggVorbis
|
||||||
|
from mutagen.flac import Picture
|
||||||
|
|
||||||
|
dflt_config_paths = ['~/.podloader.ini',
|
||||||
|
'~/.podloader/podloader.ini',
|
||||||
|
'podloader.ini',
|
||||||
|
'podloader.ini.dist']
|
||||||
|
|
||||||
|
def configParse(configfile = dflt_config_paths[-1]):
|
||||||
|
# Here we find and parse the config, then return a dict of the values.
|
||||||
|
# We COULD return a configparser object, but that's a PITA to reference.
|
||||||
|
conf = configfile
|
||||||
|
olddef = dflt_config_paths[-1]
|
||||||
|
for i, item in enumerate(dflt_config_paths):
|
||||||
|
dflt_config_paths[i] = os.path.expanduser(item)
|
||||||
|
for i, item in enumerate(dflt_config_paths):
|
||||||
|
if not dflt_config_paths[i].startswith('/'):
|
||||||
|
dflt_config_paths[i] = '{0}/{1}'.format(os.path.dirname(os.path.realpath(__file__)), item)
|
||||||
|
if configfile != olddef:
|
||||||
|
conf = configfile
|
||||||
|
else:
|
||||||
|
for p in dflt_config_paths:
|
||||||
|
if os.path.isfile(p):
|
||||||
|
conf = p
|
||||||
|
break
|
||||||
|
defconf = dflt_config_paths[-1]
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config._interpolation = configparser.ExtendedInterpolation()
|
||||||
|
config.read([defconf, conf])
|
||||||
|
config_dict = {s:dict(config.items(s)) for s in config.sections()}
|
||||||
|
# Convert the booleans to pythonic booleans in the dict, convert to ints, etc.
|
||||||
|
if config['mysql']['password'] == 'False':
|
||||||
|
config_dict['mysql']['password'] = config['mysql'].getboolean('password')
|
||||||
|
config_dict['gpg']['enabled'] = config['gpg'].getboolean('enabled')
|
||||||
|
config_dict['mysql']['port'] = config['mysql'].getint('port')
|
||||||
|
config_dict['tags']['season_pad'] = config['tags'].getint('season_pad')
|
||||||
|
config_dict['tags']['episode_pad'] = config['tags'].getint('episode_pad')
|
||||||
|
# Set some "magic" interpolation
|
||||||
|
if not config_dict['mysql']['password']:
|
||||||
|
config_dict['mysql']['conf'] = os.path.expanduser(config_dict['mysql']['conf'])
|
||||||
|
mysqlconf = configparser.ConfigParser(allow_no_value = True)
|
||||||
|
if os.path.isfile(config_dict['mysql']['conf']):
|
||||||
|
mysqlconf.read(config_dict['mysql']['conf'])
|
||||||
|
mysqlcnf_dict = {s:dict(mysqlconf.items(s)) for s in mysqlconf.sections()}
|
||||||
|
mysqlcnf = mysqlcnf_dict['client' + config_dict['mysql']['confsec']]
|
||||||
|
if 'host' in mysqlcnf:
|
||||||
|
config_dict['mysql']['host'] = mysqlcnf['host']
|
||||||
|
else:
|
||||||
|
config_dict['mysql']['host'] = 'localhost'
|
||||||
|
if 'ssl' in mysqlcnf:
|
||||||
|
config_dict['mysql']['ssl'] = {}
|
||||||
|
for c in ('ssl-ca','ssl-cert', 'ssl-key', 'ssl-cipher'):
|
||||||
|
if c in mysqlcnf:
|
||||||
|
newkey = c.replace('ssl-', '')
|
||||||
|
config_dict['mysql']['ssl'][newkey] = mysqlcnf[c]
|
||||||
|
config_dict['mysql']['user'] = mysqlcnf['user']
|
||||||
|
config_dict['mysql']['password'] = mysqlcnf['password']
|
||||||
|
if 'port' in mysqlcnf:
|
||||||
|
config_dict['mysql']['port'] = int(mysqlcnf['port'])
|
||||||
|
else:
|
||||||
|
config_dict['mysql']['port'] = 3306
|
||||||
|
del config_dict['mysql']['confsec']
|
||||||
|
mysqlcnf.clear()
|
||||||
|
mysqlcnf_dict.clear()
|
||||||
|
for s in mysqlconf.sections():
|
||||||
|
mysqlconf.remove_section(s)
|
||||||
|
else:
|
||||||
|
exit('ERROR: You specified [mysql]password as False but did not provide a valid .my.cnf path!')
|
||||||
|
config_dict['gpg']['keys'] = config_dict['gpg']['keys'].split(',')
|
||||||
|
if len(config_dict['gpg']['keys']) >= 1:
|
||||||
|
config_dict['gpg']['keys'][:] = [re.sub('^\s*(0x)?([0-9A-F]*)\s*',
|
||||||
|
'\g<2>', x).upper() for x in config_dict['gpg']['keys']]
|
||||||
|
if config_dict['gpg']['enabled'] == True:
|
||||||
|
if config_dict['gpg']['homedir'] != '':
|
||||||
|
config_dict['gpg']['homedir'] = os.path.expanduser(config_dict['gpg']['homedir'])
|
||||||
|
config_dict['local']['path'] = os.path.expanduser(config_dict['local']['path'])
|
||||||
|
config_dict['local']['mediadir'] = os.path.expanduser(config_dict['local']['mediadir'])
|
||||||
|
os.makedirs(config_dict['local']['mediadir'], exist_ok = True)
|
||||||
|
if config_dict['tags']['year'] == 'False':
|
||||||
|
config_dict['tags']['year'] = config['tags'].getboolean('year')
|
||||||
|
config_dict['tags']['img'] = os.path.expanduser(config_dict['tags']['img'])
|
||||||
|
return(config_dict)
|
||||||
|
|
||||||
|
def confArgs(conf, args):
|
||||||
|
conf['episode'] = {}
|
||||||
|
conf['episode']['title'] = args.title
|
||||||
|
conf['episode']['file_title'] = re.sub('[^A-Za-z0-9-]', '.', conf['episode']['title']).lower()
|
||||||
|
conf['episode']['season'] = str(args.season).zfill(conf['tags']['season_pad'])
|
||||||
|
conf['episode']['serial'] = str(args.episode).zfill(conf['tags']['episode_pad'])
|
||||||
|
for i in ('season', 'episode'):
|
||||||
|
del conf['tags'][i + '_pad']
|
||||||
|
conf['episode']['id'] = 'S{0}E{1}'.format(str(conf['episode']['season']),
|
||||||
|
str(conf['episode']['serial']))
|
||||||
|
conf['episode']['pretty_title'] = '{0}: {1}'.format(conf['episode']['id'], conf['episode']['title'])
|
||||||
|
if conf['tags']['track'] == 'EPISODE':
|
||||||
|
conf['tags']['track'] = conf['episode']['serial']
|
||||||
|
conf['tags']['url'] = re.sub('^(.*)SEASONEPISODE(.*)$',
|
||||||
|
'\g<1>' + conf['episode']['id'] + '\g<2>',
|
||||||
|
conf['tags']['url'])
|
||||||
|
conf['tags']['url'] = re.sub('^(.*)SEASON(.*)$',
|
||||||
|
'\g<1>' + conf['episode']['season'] + '\g<2>',
|
||||||
|
conf['tags']['url'])
|
||||||
|
conf['tags']['url'] = re.sub('^(.*)EPISODE(.*)$',
|
||||||
|
'\g<1>' + conf['episode']['serial'] + '\g<2>',
|
||||||
|
conf['tags']['url'])
|
||||||
|
conf['local']['subdir'] = re.sub('^(.*)SEASONEPISODE(.*)$',
|
||||||
|
'\g<1>' + conf['episode']['id'] + '\g<2>',
|
||||||
|
conf['local']['subdir'])
|
||||||
|
conf['local']['subdir'] = re.sub('^(.*)SEASON(.*)$',
|
||||||
|
'\g<1>' + conf['episode']['season'] + '\g<2>',
|
||||||
|
conf['local']['subdir'])
|
||||||
|
conf['local']['subdir'] = re.sub('^(.*)EPISODE(.*)$',
|
||||||
|
'\g<1>' + conf['episode']['serial'] + '\g<2>',
|
||||||
|
conf['local']['subdir'])
|
||||||
|
conf['local']['path'] = '{0}/{1}'.format(conf['local']['path'],
|
||||||
|
conf['local']['subdir'].lower())
|
||||||
|
if not conf['tags']['year']:
|
||||||
|
conf['tags']['year'] = datetime.datetime.now().year
|
||||||
|
conf['tags']['year'] = str(conf['tags']['year'])
|
||||||
|
if not os.path.isdir(conf['local']['path']):
|
||||||
|
os.makedirs(conf['local']['path'], exist_ok = True)
|
||||||
|
del conf['local']['subdir']
|
||||||
|
conf['episode']['raw'] = args.flacfile
|
||||||
|
if args.flacfile:
|
||||||
|
newpath = os.path.abspath(os.path.expanduser(args.flacfile))
|
||||||
|
if os.path.isfile(newpath):
|
||||||
|
conf['episode']['raw'] = newpath
|
||||||
|
else:
|
||||||
|
exit('ERROR: The FLAC file you specified does not seem to exist({0}). Check your path.'.format(newpath))
|
||||||
|
else:
|
||||||
|
dflt_flac_names = ['{0}.edited.flac'.format(conf['episode']['id'].lower()),
|
||||||
|
'{0}.final.flac'.format(conf['episode']['id'].lower()),
|
||||||
|
'{0}.flac'.format(conf['episode']['id'].lower())]
|
||||||
|
for f in dflt_flac_names:
|
||||||
|
if os.path.isfile('{0}/{1}'.format(conf['local']['path'], f)):
|
||||||
|
conf['episode']['raw'] = '{0}/{1}'.format(conf['local']['path'], f)
|
||||||
|
break
|
||||||
|
if not conf['episode']['raw']:
|
||||||
|
exit('ERROR: We cannot seem to locate a FLAC to convert. Try using the -f/--file argument.')
|
||||||
|
magic_file = magic.open(magic.MAGIC_MIME)
|
||||||
|
magic_file.load()
|
||||||
|
if not magic_file.file(conf['episode']['raw']) == 'audio/x-flac; charset=binary':
|
||||||
|
exit('ERROR: Your FLAC file does not seem to actually be FLAC.')
|
||||||
|
conf['flac'] = {}
|
||||||
|
conf['flac']['samples'] = subprocess.check_output(['metaflac',
|
||||||
|
'--show-total-samples',
|
||||||
|
'{0}'.format(conf['episode']['raw'])]).decode('utf-8').strip()
|
||||||
|
conf['flac']['rate'] = subprocess.check_output(['metaflac',
|
||||||
|
'--show-sample-rate',
|
||||||
|
'{0}'.format(conf['episode']['raw'])]).decode('utf-8').strip()
|
||||||
|
conf['flac']['rate'] = '{0:.2f}'.format(float(conf['flac']['rate']))
|
||||||
|
rawfilepath = os.path.abspath(os.path.expanduser(args.raw_recording))
|
||||||
|
if not os.path.isfile(rawfilepath):
|
||||||
|
exit('ERROR: the raw recording evaluated to {0} but it does not seem to exist!'.format(rawfilepath))
|
||||||
|
conf['episode']['recorded'] = (str(datetime.datetime.utcfromtimestamp(os.path.getmtime(rawfilepath)))).split('.')[0]
|
||||||
|
conf['episode']['length'] = float(conf['flac']['samples'])/float(conf['flac']['rate'])
|
||||||
|
conf['episode']['length'] = str(int(conf['episode']['length']))
|
||||||
|
if args.now:
|
||||||
|
timestamp = datetime.datetime.timestamp(datetime.datetime.now())
|
||||||
|
else:
|
||||||
|
timestamp = os.path.getmtime(conf['episode']['raw'])
|
||||||
|
conf['episode']['sha'] = {}
|
||||||
|
conf['episode']['size'] = {}
|
||||||
|
conf['episode']['released'] = (str(datetime.datetime.utcnow())).split('.')[0]
|
||||||
|
conf['episode']['month'] = datetime.datetime.fromtimestamp(timestamp).strftime('%m')
|
||||||
|
conf['episode']['day'] = datetime.datetime.fromtimestamp(timestamp).strftime('%d')
|
||||||
|
conf['episode']['file_title'] = re.sub('\.+', '.', conf['episode']['file_title'])
|
||||||
|
conf['episode']['file_title'] = '{0}.{1}'.format(conf['episode']['id'].lower(),
|
||||||
|
re.sub('\.$',
|
||||||
|
'',
|
||||||
|
conf['episode']['file_title']))
|
||||||
|
del conf['flac']
|
||||||
|
if args.editor:
|
||||||
|
del conf['tags']['editor']
|
||||||
|
conf['episode']['editor'] = args.editor
|
||||||
|
else:
|
||||||
|
conf['episode']['editor'] = conf['tags']['editor']
|
||||||
|
if conf['tags']['album'] == 'SEASON':
|
||||||
|
conf['tags']['album'] = 'Season {0}'.format(conf['episode']['season'])
|
||||||
|
conf['local']['mediadir'] = '{0}/S{1}/E{2}'.format(conf['local']['mediadir'],
|
||||||
|
conf['episode']['season'],
|
||||||
|
conf['episode']['serial'])
|
||||||
|
os.makedirs(conf['local']['mediadir'], exist_ok = True)
|
||||||
|
cc_base_url = 'https://creativecommons.org/licenses'
|
||||||
|
conf['music'] = {}
|
||||||
|
conf['music']['intro'] = {}
|
||||||
|
conf['music']['intro']['artist'] = args.intro_artist
|
||||||
|
conf['music']['intro']['title'] = args.intro_title
|
||||||
|
conf['music']['intro']['copyright'] = args.intro_copyright
|
||||||
|
conf['music']['intro']['link'] = args.intro_link
|
||||||
|
if args.intro_copyrightlink:
|
||||||
|
conf['music']['intro']['copyrightlink'] = args.intro_copyrightlink
|
||||||
|
else:
|
||||||
|
strp_cr = (re.sub('CC-?', '', args.intro_copyright, flags = re.I)).split()
|
||||||
|
if len(strp_cr) != 2:
|
||||||
|
exit('ERROR: You did not specify a copyright link and this does not seem to be a CC license!')
|
||||||
|
conf['music']['intro']['copyrightlink'] = '{0}/{1}/{2}/'.format(
|
||||||
|
cc_base_url,
|
||||||
|
strp_cr[0].lower(),
|
||||||
|
strp_cr[1])
|
||||||
|
conf['music']['outro'] = {}
|
||||||
|
conf['music']['outro']['artist'] = args.outro_artist
|
||||||
|
conf['music']['outro']['title'] = args.outro_title
|
||||||
|
conf['music']['outro']['copyright'] = args.outro_copyright
|
||||||
|
conf['music']['outro']['link'] = args.outro_link
|
||||||
|
if args.intro_copyrightlink:
|
||||||
|
conf['music']['outro']['copyrightlink'] = args.outro_copyrightlink
|
||||||
|
else:
|
||||||
|
strp_cr = (re.sub('CC-?', '', args.outro_copyright, flags = re.I)).split()
|
||||||
|
conf['music']['outro']['copyrightlink'] = '{0}/{1}/{2}/'.format(
|
||||||
|
cc_base_url,
|
||||||
|
strp_cr[0].lower(),
|
||||||
|
strp_cr[1])
|
||||||
|
return(conf)
|
||||||
|
|
||||||
|
def transcodeMP3(conf):
|
||||||
|
mediatype = 'mp3'
|
||||||
|
mediadir = '{0}/{1}'.format(conf['local']['mediadir'], mediatype)
|
||||||
|
mediafile = '{0}/{1}.{2}'.format(mediadir,
|
||||||
|
conf['episode']['file_title'],
|
||||||
|
mediatype)
|
||||||
|
if os.path.isfile(mediafile):
|
||||||
|
os.remove(mediafile)
|
||||||
|
os.makedirs(mediadir, exist_ok = True)
|
||||||
|
print('{0}: Transcoding to {1}...'.format(datetime.datetime.now(), mediatype))
|
||||||
|
subprocess.call(['ffmpeg', '-stats', '-loglevel', '0', '-i',
|
||||||
|
conf['episode']['raw'], '-b:a', '128k', '-ac','1', '-joint_stereo', '1',
|
||||||
|
mediafile])
|
||||||
|
return(mediafile)
|
||||||
|
|
||||||
|
def transcodeOGG(conf):
|
||||||
|
mediatype = 'ogg'
|
||||||
|
mediadir = '{0}/{1}'.format(conf['local']['mediadir'], mediatype)
|
||||||
|
mediafile = '{0}/{1}.{2}'.format(mediadir,
|
||||||
|
conf['episode']['file_title'],
|
||||||
|
mediatype)
|
||||||
|
if os.path.isfile(mediafile):
|
||||||
|
os.remove(mediafile)
|
||||||
|
os.makedirs(mediadir, exist_ok = True)
|
||||||
|
print('{0}: Transcoding to {1}...'.format(datetime.datetime.now(), mediatype))
|
||||||
|
subprocess.call(['ffmpeg', '-stats', '-loglevel', '0', '-i',
|
||||||
|
conf['episode']['raw'], '-qscale:a', '8', '-ac','1', '-joint_stereo', '1',
|
||||||
|
mediafile])
|
||||||
|
return(mediafile)
|
||||||
|
|
||||||
|
def tagMP3(conf, mediafile):
|
||||||
|
# This appears to not work.
|
||||||
|
# http://id3.org/id3v2.3.0#Attached_picture
|
||||||
|
# http://id3.org/id3v2.4.0-frames (section 4.14)
|
||||||
|
# https://stackoverflow.com/questions/7275710/mutagen-how-to-detect-and-embed-album-art-in-mp3-flac-and-mp4
|
||||||
|
# https://stackoverflow.com/questions/409949/how-do-you-embed-album-art-into-an-mp3-using-python
|
||||||
|
magic_file = magic.open(magic.MAGIC_MIME)
|
||||||
|
magic_file.load()
|
||||||
|
imgmime = magic_file.file(conf['tags']['img']).split(';')[0]
|
||||||
|
with open(conf['tags']['img'], 'rb') as f:
|
||||||
|
img_data = f.read()
|
||||||
|
print('{0}: Now adding tags to {1}...'.format(datetime.datetime.now(), mediafile))
|
||||||
|
tag = ID3(mediafile)
|
||||||
|
tag.add(TALB(encoding = 0, text = [conf['tags']['album']]))
|
||||||
|
tag.add(APIC(encoding = 0, mime = imgmime, type = 3,
|
||||||
|
desc = conf['tags']['artist'], data = img_data))
|
||||||
|
tag.add(TDRC(encoding = 0, text = ['{0}.{1}.{2}'.format(conf['tags']['year'],
|
||||||
|
conf['episode']['month'],
|
||||||
|
conf['episode']['day'])]))
|
||||||
|
tag.add(TENC(encoding = 0, text = [conf['tags']['encoded']]))
|
||||||
|
tag.add(TRCK(encoding = 0, text = [conf['tags']['track']]))
|
||||||
|
tag.add(COMM(encoding = 0, lang = '\x00\x00\x00', desc = '',
|
||||||
|
text = [conf['tags']['comment']]))
|
||||||
|
tag.add(WXXX(encoding = 0, desc = '', url = conf['tags']['url']))
|
||||||
|
tag.add(TCON(encoding = 0, text = [conf['tags']['genre']]))
|
||||||
|
tag.add(TIT2(encoding = 0, text = [conf['episode']['pretty_title']]))
|
||||||
|
tag.add(TPE1(encoding = 0, text = [conf['tags']['artist']]))
|
||||||
|
tag.add(TCOP(encoding = 0, text = [conf['tags']['copyright']]))
|
||||||
|
tag.save()
|
||||||
|
|
||||||
|
def tagOGG(conf, mediafile):
|
||||||
|
# It seems we can't use this method.
|
||||||
|
# https://mutagen.readthedocs.io/en/latest/user/vcomment.html
|
||||||
|
# https://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
|
||||||
|
# https://xiph.org/flac/format.html#metadata_block_picture
|
||||||
|
# https://github.com/quodlibet/mutagen/issues/200
|
||||||
|
magic_file = magic.open(magic.MAGIC_MIME)
|
||||||
|
magic_file.load()
|
||||||
|
imgmime = magic_file.file(conf['tags']['img']).split(';')[0]
|
||||||
|
with open(conf['tags']['img'], 'rb') as f:
|
||||||
|
img_b64 = base64.b64encode(f.read())
|
||||||
|
print('{0}: Now adding tags to {1}...'.format(datetime.datetime.now(), mediafile))
|
||||||
|
tag = OggVorbis(mediafile)
|
||||||
|
tag['TITLE'] = conf['episode']['pretty_title']
|
||||||
|
tag['ARTIST'] = conf['tags']['artist']
|
||||||
|
tag['ALBUM'] = conf['tags']['album']
|
||||||
|
tag['DATE'] = '{0}.{1}.{2}'.format(conf['tags']['year'],
|
||||||
|
conf['episode']['month'],
|
||||||
|
conf['episode']['day'])
|
||||||
|
tag['TRACKNUMBER'] = conf['tags']['track']
|
||||||
|
tag['GENRE'] = conf['tags']['genre']
|
||||||
|
tag['DESCRIPTION'] = conf['tags']['comment']
|
||||||
|
tag['COPYRIGHT'] = conf['tags']['copyright']
|
||||||
|
tag['CONTACT'] = conf['tags']['url']
|
||||||
|
tag['ENCODED-BY'] = conf['tags']['encoded']
|
||||||
|
tag['ENCODER'] = conf['tags']['encoded']
|
||||||
|
tag['METADATA_BLOCK_PICTURE'] = img_b64.decode('utf-8')
|
||||||
|
tag.save()
|
||||||
|
|
||||||
|
def getSHA256(mediafile):
|
||||||
|
print('{0}: Generating SHA256 for {1}...'.format(datetime.datetime.now(),
|
||||||
|
mediafile))
|
||||||
|
filehash = hashlib.sha256()
|
||||||
|
with open(mediafile, 'rb') as f:
|
||||||
|
for chunk in iter(lambda: f.read(4096), b""):
|
||||||
|
filehash.update(chunk)
|
||||||
|
return(filehash.hexdigest())
|
||||||
|
|
||||||
|
def getSize(mediafile):
|
||||||
|
filesize = os.path.getsize(mediafile)
|
||||||
|
return(filesize)
|
||||||
|
|
||||||
|
def dbEntry(conf):
|
||||||
|
print('{0}: Inserting into the {1}.{2}@{3} table...'.format(datetime.datetime.now(),
|
||||||
|
conf['mysql']['db'],
|
||||||
|
conf['mysql']['table'],
|
||||||
|
conf['mysql']['host']))
|
||||||
|
ssl = False
|
||||||
|
if 'ssl' in conf['mysql']:
|
||||||
|
ssl = conf['mysql']['ssl']
|
||||||
|
vals = "'{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}','{13}','{14}','{15}','{16}','{17}','{18}','{19}'".format(conf['episode']['id'],
|
||||||
|
conf['episode']['file_title'],
|
||||||
|
conf['episode']['sha']['mp3'],
|
||||||
|
conf['episode']['sha']['ogg'],
|
||||||
|
conf['episode']['size']['mp3'],
|
||||||
|
conf['episode']['size']['ogg'],
|
||||||
|
conf['episode']['length'],
|
||||||
|
re.sub("'", "\\'", conf['episode']['editor']),
|
||||||
|
re.sub("'", "\\'", conf['music']['intro']['title']),
|
||||||
|
re.sub("'", "\\'", conf['music']['intro']['artist']),
|
||||||
|
conf['music']['intro']['link'],
|
||||||
|
re.sub("'", "\\'", conf['music']['intro']['copyright']),
|
||||||
|
conf['music']['intro']['copyrightlink'],
|
||||||
|
re.sub("'", "\\'", conf['music']['outro']['title']),
|
||||||
|
re.sub("'", "\\'", conf['music']['outro']['artist']),
|
||||||
|
conf['music']['outro']['link'],
|
||||||
|
re.sub("'", "\\'", conf['music']['outro']['copyright']),
|
||||||
|
conf['music']['outro']['copyrightlink'],
|
||||||
|
conf['episode']['recorded'],
|
||||||
|
conf['episode']['released'])
|
||||||
|
|
||||||
|
|
||||||
|
conn = pymysql.connect(host = conf['mysql']['host'],
|
||||||
|
port = conf['mysql']['port'],
|
||||||
|
user = conf['mysql']['user'],
|
||||||
|
passwd = conf['mysql']['password'],
|
||||||
|
db = conf['mysql']['db'],
|
||||||
|
ssl = ssl,
|
||||||
|
autocommit = True)
|
||||||
|
cur = conn.cursor()
|
||||||
|
query = 'INSERT INTO {0} ({1}) VALUES ({2})'.format(conf['mysql']['table'],
|
||||||
|
conf['mysql']['cols'],
|
||||||
|
vals)
|
||||||
|
try:
|
||||||
|
cur.execute(query)
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
except:
|
||||||
|
print('{0}: There seems to have been some error when inserting into the DB. Check access (or it is a dupe).'.format(
|
||||||
|
datetime.datetime.now()))
|
||||||
|
|
||||||
|
def signEp(mediatype):
|
||||||
|
os.makedirs('{0}/gpg'.format(conf['local']['mediadir']), exist_ok = True)
|
||||||
|
sigfile = '{0}/gpg/{1}.{2}.asc'.format(conf['local']['mediadir'],
|
||||||
|
conf['episode']['file_title'],
|
||||||
|
mediatype)
|
||||||
|
os.environ['GNUPGHOME'] = conf['gpg']['homedir']
|
||||||
|
vrfykeys = []
|
||||||
|
sigs = {}
|
||||||
|
gpg = gpgme.Context()
|
||||||
|
gpg.armor = True
|
||||||
|
for k in conf['gpg']['keys']:
|
||||||
|
if gpg.get_key(k, True).can_sign:
|
||||||
|
# it seems pygpgme does not allow signing with subkeys. sad day. gpg.signkeys complains if you pass it Subkey objects.
|
||||||
|
#subkeys = []
|
||||||
|
#for i in gpg.get_key(k, True).subkeys:
|
||||||
|
# subkeys.append(i.fpr)
|
||||||
|
#indexnum = [x for x, s in enumerate(subkeys) if k in s][0]
|
||||||
|
#vrfykeys.append(gpg.get_key(k, True).subkeys[indexnum].fpr)
|
||||||
|
if gpg.get_key(k, True).subkeys[0].fpr not in vrfykeys:
|
||||||
|
vrfykeys.append(gpg.get_key(k, True).subkeys[0].fpr)
|
||||||
|
data_in = '{0}/{1}/{2}.{3}'.format(conf['local']['mediadir'],
|
||||||
|
mediatype,
|
||||||
|
conf['episode']['file_title'],
|
||||||
|
mediatype)
|
||||||
|
print('{0}: Checking for existing GPG signatures (and skipping if we signed)...'.format(datetime.datetime.now()))
|
||||||
|
if os.path.isfile(sigfile):
|
||||||
|
with open(sigfile, 'rb') as s:
|
||||||
|
with open(data_in, 'rb') as f:
|
||||||
|
for k in gpg.verify(s, f, None):
|
||||||
|
try:
|
||||||
|
sigs[gpg.get_key(k.fpr, True).subkeys[0].fpr] = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
for k in vrfykeys:
|
||||||
|
if k not in sigs:
|
||||||
|
sigkeys = []
|
||||||
|
if gpg.get_key(k, True).can_sign:
|
||||||
|
print('{0}: Signing with key {1}...'.format(datetime.datetime.now(),
|
||||||
|
k))
|
||||||
|
sigkeys.append(gpg.get_key(k, True))
|
||||||
|
gpg.signers = sigkeys
|
||||||
|
with open(sigfile, 'ab') as s:
|
||||||
|
with open(data_in, 'rb') as f:
|
||||||
|
gpg.sign(f, s, gpgme.SIG_MODE_DETACH)
|
||||||
|
return(sigfile)
|
||||||
|
|
||||||
|
def uploadFile():
|
||||||
|
print('{0}: Syncing files to server...'.format(datetime.datetime.now()))
|
||||||
|
subprocess.call(['rsync',
|
||||||
|
'-a',
|
||||||
|
'{0}'.format(conf['local']['mediadir']),
|
||||||
|
'{0}@{1}:{2}S{3}/.'.format(conf['rsync']['user'],
|
||||||
|
conf['rsync']['host'],
|
||||||
|
conf['rsync']['path'],
|
||||||
|
conf['episode']['season'])])
|
||||||
|
|
||||||
|
def argParse():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description = 'PodLoader - a script to assist in Textpattern-powered podcasts',
|
||||||
|
prog = 'podloader v1.0')
|
||||||
|
requiredArgs = parser.add_argument_group('REQUIRED arguments')
|
||||||
|
requiredArgs.add_argument('-t',
|
||||||
|
'--title',
|
||||||
|
dest = 'title',
|
||||||
|
required = True,
|
||||||
|
help = "The episode's title (as it will appear in meta information).")
|
||||||
|
requiredArgs.add_argument('-e',
|
||||||
|
'--episode',
|
||||||
|
dest = 'episode',
|
||||||
|
required = True,
|
||||||
|
type = int,
|
||||||
|
help = "The episode number for this episode.")
|
||||||
|
requiredArgs.add_argument('-s',
|
||||||
|
'--season',
|
||||||
|
dest = 'season',
|
||||||
|
required = True,
|
||||||
|
type = int,
|
||||||
|
help = "The season number this episode is in.")
|
||||||
|
requiredArgs.add_argument('-r',
|
||||||
|
'--raw-recording',
|
||||||
|
dest = 'raw_recording',
|
||||||
|
required = True,
|
||||||
|
help = "The path to a single-track *raw* recording. This file is used to get the timestamp of recording.")
|
||||||
|
requiredArgs.add_argument('-i:a',
|
||||||
|
'--intro-artist',
|
||||||
|
dest = 'intro_artist',
|
||||||
|
required = True,
|
||||||
|
help = "The artist for the intro music.")
|
||||||
|
requiredArgs.add_argument('-i:t',
|
||||||
|
'--intro-title',
|
||||||
|
dest = 'intro_title',
|
||||||
|
required = True,
|
||||||
|
help = "The title for the intro music.")
|
||||||
|
requiredArgs.add_argument('-i:l',
|
||||||
|
'--intro-link',
|
||||||
|
dest = 'intro_link',
|
||||||
|
required = True,
|
||||||
|
help = "The link to the intro track (i.e. page to more information about the track).")
|
||||||
|
requiredArgs.add_argument('-i:c',
|
||||||
|
'--intro-copyright',
|
||||||
|
dest = 'intro_copyright',
|
||||||
|
required = True,
|
||||||
|
help = "The copyright for the intro music. If it's a Creative Commons type, you do not need to include a copyright link. e.g. '-i:c \"CC-BY-SA 3.0\"'")
|
||||||
|
requiredArgs.add_argument('-o:a',
|
||||||
|
'--outro-artist',
|
||||||
|
dest = 'outro_artist',
|
||||||
|
required = True,
|
||||||
|
help = "The artist for the outro music.")
|
||||||
|
requiredArgs.add_argument('-o:t',
|
||||||
|
'--outro-title',
|
||||||
|
dest = 'outro_title',
|
||||||
|
required = True,
|
||||||
|
help = "The title for the outro music.")
|
||||||
|
requiredArgs.add_argument('-o:l',
|
||||||
|
'--outro-link',
|
||||||
|
dest = 'outro_link',
|
||||||
|
required = True,
|
||||||
|
help = "The link to the outro track (i.e. page to more information about the track).")
|
||||||
|
requiredArgs.add_argument('-o:c',
|
||||||
|
'--outro-copyright',
|
||||||
|
dest = 'outro_copyright',
|
||||||
|
required = True,
|
||||||
|
help = "The copyright for the outro music. If it's a Creative Commons type, you do not need to include a copyright link. e.g. '-i:c \"CC-BY-SA 3.0\"'")
|
||||||
|
parser.add_argument('-i:cl',
|
||||||
|
'--intro-copyrightlink',
|
||||||
|
dest = 'intro_copyrightlink',
|
||||||
|
default = False,
|
||||||
|
help = "The link to the copyright terms for the intro. Optional if it's a CC license.")
|
||||||
|
parser.add_argument('-o:cl',
|
||||||
|
'--outro-copyrightlink',
|
||||||
|
default = False,
|
||||||
|
dest = 'outro_copyrightlink',
|
||||||
|
help = "The link to the copyright terms for the outro. Optional if it's a CC license.")
|
||||||
|
parser.add_argument('-d',
|
||||||
|
'--editor',
|
||||||
|
dest = 'editor',
|
||||||
|
help = 'The audio editor for the episode. Can (should) contain HTML link to editor (e.g. \'<a href="https://editorname.tld">Editor Name</a>\'')
|
||||||
|
parser.add_argument('-f',
|
||||||
|
'--file',
|
||||||
|
dest = 'flacfile',
|
||||||
|
default = False,
|
||||||
|
help = "The (final edit) FLAC file to be used for the episode. If not specified, we'll try to guess.")
|
||||||
|
parser.add_argument('-n',
|
||||||
|
'--now',
|
||||||
|
dest = 'now',
|
||||||
|
default = False,
|
||||||
|
action = 'store_true',
|
||||||
|
help = "Instead of getting the date based on the time of the file, use today's date (for media tags).")
|
||||||
|
try:
|
||||||
|
args = parser.parse_args()
|
||||||
|
print('{0}: Starting.'.format(datetime.datetime.now()))
|
||||||
|
except (NameError, TypeError):
|
||||||
|
parser.print_help()
|
||||||
|
exit(1)
|
||||||
|
return(args)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
conf = confArgs(configParse(), argParse())
|
||||||
|
mp3 = transcodeMP3(conf)
|
||||||
|
tagMP3(conf, mp3)
|
||||||
|
ogg = transcodeOGG(conf)
|
||||||
|
tagOGG(conf, ogg)
|
||||||
|
conf['episode']['sha']['mp3'] = getSHA256(mp3)
|
||||||
|
conf['episode']['sha']['ogg'] = getSHA256(ogg)
|
||||||
|
conf['episode']['size']['mp3'] = getSize(mp3)
|
||||||
|
conf['episode']['size']['ogg'] = getSize(ogg)
|
||||||
|
dbEntry(conf)
|
||||||
|
signEp('mp3')
|
||||||
|
signEp('ogg')
|
||||||
|
uploadFile()
|
||||||
|
print('{0}: Finished.'.format(datetime.datetime.now()))
|
Reference in New Issue
Block a user