commit
633057628e
3 changed files with 187 additions and 0 deletions
@ -0,0 +1 @@ |
|||
fridaydisco-*/* |
@ -0,0 +1,44 @@ |
|||
# Fridaydiso |
|||
|
|||
## Was ist die Fridaydisco? |
|||
Mehr oder weniger regelmäßig freitags nachmittags/abends/nachts treffen sich einige coole Menschen auf Mastodon unter dem Hashtag `#fridaydisco`. |
|||
Dort werden dann Links zu verschiedensten Songs auf YouTube geteilt. |
|||
|
|||
## Was tut fridaydisco.py? |
|||
Das script `fridaydisco.py` extrahiert die Links zu den Songs aus den Toots. |
|||
Die Songs werden als mp3 heruntergeladen und in einem Ordner mit dem aktuellen Datum gespeichert. |
|||
|
|||
## Benutzung von fridaydisco.py |
|||
### Benötigte Programme und Bibliotheken |
|||
#### notwendig |
|||
- python3.x |
|||
- youtube_dl |
|||
#### optional |
|||
- mplayer |
|||
|
|||
### Als live Player unter Linux (ungetestet - funktioniert in der Theorie) |
|||
- `cd fridaydisco` |
|||
- `python fridaydisco.py` |
|||
- create a cron-job or systemd-timer to run `python fridaydisco.py` every few minutes |
|||
- play music with `mplayer fridaydisco-YYYY-MM-DD/*` |
|||
|
|||
### Zur Archivierung |
|||
- `cd fridaydisco` |
|||
- `python fridaydisco.py` |
|||
Bei wiederholter Ausführung des Scripts werden lediglich neue Songs heruntergeladen |
|||
|
|||
### Funktion |
|||
- es werden aktuell immer die 80 neuesten Toots abgefragt. Als Api-Endpoint wird `https://mastodon.matrix.org/api/v1/timelines/tag/fridaydisco` verwendet. |
|||
- es werden nur öffentliche Toots mit dem aktuellem Datum analysiert |
|||
- es werden nur Links der Form `https://www.youtube.com/watch?v=` berücksichtigt |
|||
- es werden keine vollständigen Playlists von YouTube heruntergeladen |
|||
- es wird eine Datei mit Namen `fridaydisco-YYYY-MM-DD.json` erstellt, hier werden zu jedem Song Titel, Länge, Link und Dateiname gespeichert. Schlägt ein Download fehl wird sein Link in eine eigene Liste aufgenommen. So können fehlgeschlagene Downloads später manuell wiederholt werden. |
|||
alle Dateien werden in einem Ordner mit Namen `fridaydisco-YYYY-MM-DD/` gespeichert |
|||
|
|||
### Fehlende Funktionen |
|||
- Konsolenparameter für: |
|||
- Mastodon Instanz `-i` |
|||
- Datum `-d` |
|||
- Anzahl der abgefragten Toots `-n` |
|||
- Abgefragtes Hashtag `-t` |
|||
- Toots die nach Mitternacht getrötet werden sollten optional weiterhin im Ordner vom Vortag gespeichert werden. Insbesondere für den 'Live-Modus' nützlich. |
@ -0,0 +1,142 @@ |
|||
import requests |
|||
import os |
|||
import json |
|||
import youtube_dl |
|||
from datetime import date |
|||
|
|||
dl_options = { |
|||
'format': 'bestaudio/best', |
|||
'noplaylist': True, |
|||
'keep_video': False, |
|||
'prefer_ffmpeg': True, |
|||
'postprocessors': [{ |
|||
'key': 'FFmpegExtractAudio', |
|||
'preferredcodec': 'mp3', |
|||
'preferredquality': '192', }] |
|||
} |
|||
|
|||
date = date.today().strftime("%Y-%m-%d") |
|||
|
|||
dir_name = "fridaydisco-" + date |
|||
|
|||
def get_info(url): |
|||
with youtube_dl.YoutubeDL(dl_options) as ytdl: |
|||
try: |
|||
info_dict = ytdl.extract_info(url) |
|||
info = { |
|||
'title': info_dict["title"], |
|||
'duration': info_dict["duration"] |
|||
} |
|||
return info |
|||
except ( |
|||
youtube_dl.utils.DownloadError, |
|||
youtube_dl.utils.ContentTooShortError, |
|||
youtube_dl.utils.ExtractorError, |
|||
youtube_dl.utils.UnavailableVideoError |
|||
) as e: |
|||
return { 'title':'Download Failed!','duration':0} |
|||
|
|||
def dl_song(url, title): |
|||
dl_options["outtmpl"] = f'{dir_name}/{title}.mp3' |
|||
with youtube_dl.YoutubeDL(dl_options) as ytdl: |
|||
try: |
|||
ytdl.download([url]) |
|||
return dl_options["outtmpl"] |
|||
except ( |
|||
youtube_dl.utils.DownloadError, |
|||
youtube_dl.utils.ContentTooShortError, |
|||
youtube_dl.utils.ExtractorError, |
|||
youtube_dl.utils.UnavailableVideoError |
|||
) as e: |
|||
return "Download Failed!" |
|||
|
|||
json_file_name = dir_name + "/fridaydisco-" + date + ".json" |
|||
links_json = {} |
|||
if not os.path.exists(json_file_name): |
|||
if not os.path.exists(dir_name): |
|||
os.mkdir(dir_name, 0o0755 ) |
|||
print("Created Directory: " + dir_name) |
|||
else: |
|||
print("Using Directory: " + dir_name) |
|||
links_file = open(json_file_name, 'w') |
|||
links_json["songs"] = [] |
|||
links_json["failed"] = [] |
|||
print("Creadted File: " + json_file_name) |
|||
else: |
|||
links_file = open(json_file_name, 'r') |
|||
links_json = json.load(links_file) |
|||
print("Using Directory: " + dir_name) |
|||
print("Using File: " + json_file_name) |
|||
|
|||
failed_links = [] |
|||
links = [] |
|||
for elem in links_json["songs"]: |
|||
links.append(elem["url"]) |
|||
|
|||
youtube_base = "https://www.youtube.com/watch?v=" |
|||
|
|||
print("Getting 80 newest Toots from: https://mastodon.matrix.org/api/v1/timelines/tag/fridaydisco") |
|||
response = requests.get("https://mastodon.matrix.org/api/v1/timelines/tag/fridaydisco?limit=80") |
|||
posts = json.loads(response.text) |
|||
|
|||
print("Looking for public posts with timestamp: " + date) |
|||
print("Collecting Urls with this basepath: " + youtube_base) |
|||
|
|||
dl_counter = 0 |
|||
for post in posts: |
|||
if post["visibility"] == "public": |
|||
post_date_end = post["created_at"].find("T") |
|||
post_date = post["created_at"][0:post_date_end] |
|||
if post_date == date: |
|||
content = post["content"] |
|||
cursor_pos = 0 |
|||
content_length = len(content) |
|||
while cursor_pos < content_length: |
|||
link_start = content.find(youtube_base, cursor_pos, content_length) |
|||
if link_start == -1: |
|||
break |
|||
link_end = content.find("\" ", link_start, content_length) |
|||
full_link = content[link_start:link_end] |
|||
link_params_start = full_link.find("&") |
|||
if link_params_start != -1: |
|||
full_link = full_link[0:link_params_start] |
|||
if full_link not in links: |
|||
song_info = get_info(full_link) |
|||
song_name_no_spaces = song_info["title"].replace(" ", "_") |
|||
file_name = dl_song(full_link, song_name_no_spaces) |
|||
if song_info["title"] != "Download Failed!" and file_name != "Download Failed!": |
|||
links_json["songs"].append({ |
|||
"title":song_info["title"], |
|||
"duration":song_info["duration"], |
|||
"url":full_link, |
|||
"file":file_name |
|||
}) |
|||
links.append(full_link) |
|||
dl_counter += 1 |
|||
else: |
|||
if file_name == "Download Failed!" and song_info["title"] != "Download Failed!": |
|||
links_json["failed"].append({ |
|||
"url":full_link, |
|||
"title":song_info["title"], |
|||
"file":"none" |
|||
}) |
|||
else: |
|||
links_json["failed"].append({ |
|||
"url":full_link, |
|||
"title":"unknown", |
|||
"file":"none" |
|||
}) |
|||
failed_links.append(full_link) |
|||
cursor_pos = link_end |
|||
|
|||
with open(json_file_name, 'w') as outfile: |
|||
json.dump(links_json, outfile) |
|||
|
|||
print("Downloaded " + str(dl_counter) + " Songs") |
|||
print("Directory " + dir_name + " contains " + str(len(links)) + " Songs now") |
|||
print(str(len(failed_links)) + " Downloads failed!") |
|||
if len(failed_links) > 0: |
|||
print("Failed to download:") |
|||
for link in failed_links: |
|||
print("\t" + link) |
|||
print("Done") |
Loading…
Reference in new issue