Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2fddbaa
Merge pull request #18 from arizonagroovejet/content-disposition
brtmr Mar 18, 2018
437cfb5
Added option to override file names
antfred Aug 30, 2018
0dc074e
Added option to override file names
antfred Aug 30, 2018
d848909
Added multithreading to the download
Sep 8, 2018
d3f9add
Added multithreading to the download
Sep 8, 2018
2c12ec0
Add default configuration
n0trax Mar 31, 2019
0144990
Add support for audio/x-m4a
timbroder Apr 19, 2019
3196062
Bump requests from 2.11.1 to 2.20.0
dependabot[bot] Oct 2, 2019
f66ccf3
Merge pull request #24 from timbroder/add_x-m4a
brtmr Nov 2, 2019
963d0ab
Merge pull request #25 from brtmr/dependabot/pip/requests-2.20.0
brtmr Nov 2, 2019
48896e3
Merge pull request #23 from n0trax/add-default-config
brtmr Nov 2, 2019
abf6f75
Merge branch 'master' of https://github.com/brtmr/podfox
Jun 7, 2020
9b003a2
Merge branch 'master' of https://github.com/brtmr/podfox
Jun 7, 2020
e7599d8
Added mimetype "audio/x-mpeg" and check for troubling or missing publ…
hiwixxl Sep 27, 2020
4f1e0d1
Merge pull request #32 from hiwixxl/master
brtmr Sep 27, 2020
5b07270
Allow usage of later versions of feedparser
isakrubin Dec 17, 2020
23c354d
Added a progress bar while downloading episodes
Jan 17, 2021
6574d0f
Added a progress bar while downloading episodes
Jan 17, 2021
f681340
Progress bar now autoscales
Jan 17, 2021
e728c08
Progress bar now autoscales
Jan 17, 2021
caac8b2
Add prune command
fredthomsen May 16, 2021
bf2eb08
Update feedparser version for py39
fredthomsen Jun 7, 2021
16e65e2
Merge pull request #43 from fredthomsen/addPruneCommand
brtmr Sep 2, 2021
462d88f
Merge pull request #39 from isakrubin/master
brtmr Sep 2, 2021
e28d858
Merge branch 'master' into py39UpdateFeedparserVersion
brtmr Sep 2, 2021
c3965ba
Merge pull request #44 from fredthomsen/py39UpdateFeedparserVersion
brtmr Sep 2, 2021
9bd6bc6
Limit file name length to 120 characters
antfred Feb 26, 2022
5f6e9a9
Limit file name length to 120 characters
antfred Feb 26, 2022
2624fb5
Resolved merge conflict
antfred Oct 15, 2022
a39ab42
Merge branch 'brtmr-master'
antfred Oct 15, 2022
07237cf
Resolved merge conflict
antfred Oct 15, 2022
b42673d
Resolved merge conflict
antfred Oct 15, 2022
d05faa8
Resolved merge conflict
antfred Oct 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 61 additions & 23 deletions podfox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
podfox.py update [<shortname>] [-c=<path>]
podfox.py feeds [-c=<path>]
podfox.py episodes <shortname> [-c=<path>]
podfox.py download [<shortname> --how-many=<n>] [-c=<path>]
podfox.py download [<shortname> --how-many=<n>] [--rename-files] [-c=<path>]
podfox.py rename <shortname> <newname> [-c=<path>]
podfox.py prune [<shortname> --maxage-days=<n>]

Expand All @@ -21,7 +21,9 @@
from colorama import Fore, Back, Style
from docopt import docopt
from os.path import expanduser
from urllib.parse import urlparse
from sys import exit
from tqdm import tqdm
import colorama
import datetime
import feedparser
Expand All @@ -31,6 +33,10 @@
import requests
import sys
import re
import concurrent.futures
import threading
import logging
logging.basicConfig(level=logging.WARNING)

# RSS datetimes follow RFC 2822, same as email headers.
# this is the chain of stackoverflow posts that led me to believe this is true.
Expand All @@ -40,11 +46,12 @@
# how-to-parse-a-rfc-2822-date-time-into-a-python-datetime

from email.utils import parsedate
from time import mktime
from time import mktime, localtime, strftime

CONFIGURATION_DEFAULTS = {
"podcast-directory": "~/Podcasts",
"maxnum": 5000,
"maxage-days": 0,
"mimetypes": [ "audio/aac",
"audio/ogg",
"audio/mpeg",
Expand Down Expand Up @@ -219,30 +226,60 @@ def episodes_from_feed(d):
return episodes


def download_multiple(feed, maxnum):
for episode in feed['episodes']:
if maxnum == 0:
break
if not episode['downloaded'] and not episode_too_old(episode, CONFIGURATION['maxage-days']):
episode['filename'] = download_single(feed['shortname'], episode['url'])
episode['downloaded'] = True
maxnum -= 1
def download_multiple(feed, maxnum, rename):
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
# parse up to maxnum of the not downloaded episodes
future_to_episodes = {}
for episode in list(filter(lambda ep: not ep['downloaded'] and not episode_too_old(ep, CONFIGURATION['maxage-days']), feed['episodes']))[:maxnum]:
filename = ""

if rename:
title = episode['title']
for c in '<>\"|*%?\\/':
title = title.replace(c, "")
title = title.replace(" ", "_").replace("’", "'").replace("—", "-").replace(":", ".")
# Shorten the title to max 120 characters
title = title[:120]
extension = os.path.splitext(urlparse(episode['url'])[2])[1]
filename = "{}_{}{}".format(strftime('%Y-%m-%d', localtime(episode['published'])),
title, extension)


future_to_episodes[executor.submit(download_single, feed['shortname'], episode['url'], filename)]=episode

for future in concurrent.futures.as_completed(future_to_episodes):
episode = future_to_episodes[future]
try:
filename = future.result()
episode['filename'] = filename if filename else ''
episode['downloaded'] = True if filename else False
except Exception as exc:
print('%r generated an exception: %s' % (episode['title'], exc))
overwrite_config(feed)

def download_single(folder, url):
print(url)

def download_single(folder, url, filename=""):
logging.info("{}: Parsing URL {}".format(threading.current_thread().name, url))
base = CONFIGURATION['podcast-directory']
r = requests.get(url.strip(), stream=True)
try:
filename = re.findall('filename="([^"]+)', r.headers['content-disposition'])[0]
except:
filename = get_filename_from_url(url)
print_green("{:s} downloading".format(filename))
with open(os.path.join(base, folder, filename), 'wb') as f:
for chunk in r.iter_content(chunk_size=1024**2):
f.write(chunk)
print("done.")
if not filename:
try:
filename=re.findall('filename="([^"]+)',r.headers['content-disposition'])[0]
except:
filename = get_filename_from_url(url)
logging.info("{}: {:s} downloading".format(threading.current_thread().name, filename))

try:
with open(os.path.join(base, folder, filename), 'wb') as f:
pbar = tqdm(total=int(r.headers['Content-Length']), unit='B', unit_scale=True, unit_divisor=1024)
pbar.set_description(filename if len(filename)<20 else filename[:20])
for chunk in r.iter_content(chunk_size=1024**2):
f.write(chunk)
pbar.update(len(chunk))
except EnvironmentError:
print_err("{}: Error while writing {}".format(threading.current_thread().name, filename))
return ''
logging.info("{}: done.".format(threading.current_thread().name))
return filename

def available_feeds():
Expand Down Expand Up @@ -393,19 +430,20 @@ def main():
maxnum = int(arguments['--how-many'])
else:
maxnum = CONFIGURATION['maxnum']
rename_files = bool(arguments['--rename-files'])
#download episodes for a specific feed
if arguments['<shortname>']:
feed = find_feed(arguments['<shortname>'])
if feed:
download_multiple(feed, maxnum)
download_multiple(feed, maxnum, rename_files)
exit(0)
else:
print_err("feed {} not found".format(arguments['<shortname>']))
exit(-1)
#download episodes for all feeds.
else:
for feed in available_feeds():
download_multiple(feed, maxnum)
download_multiple(feed, maxnum, rename_files)
exit(0)
if arguments['rename']:
rename(arguments['<shortname>'], arguments['<newname>'])
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ colorama==0.3.7
docopt==0.6.2
feedparser==6.0.2
requests==2.20.0
tqdm=4.48.2
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
required = f.read().splitlines()

setup(name='podfox',
version='0.1.1',
version='0.1.2',
description='Podcatcher for the terminal',
url='http://github.com/brtmr/podfox',
author='Bastian Reitemeier',
Expand Down