Initial commit
This commit is contained in:
29
examples/README.ytfeed
Normal file
29
examples/README.ytfeed
Normal file
@@ -0,0 +1,29 @@
|
||||
```
|
||||
____ __ __ __
|
||||
/_/ / /_ /_/ /_/ _/ /\
|
||||
__/ / / /_ /_ /_/ /__\ public domain 2020-2025
|
||||
```
|
||||
|
||||
ytfeed(6) is a small and simple YouTube client made using menu(1). ytfeed(6)
|
||||
exists as a demonstration of menu(1), menu(1) was written as a component of
|
||||
ytfeed(6), and they were both written for my own personal use.
|
||||
|
||||
There are many components of ytfeed that work independently of it.
|
||||
|
||||
ytfeed.aggregate(1) is a tool to merge many catenated YouTube RSS feeds into
|
||||
one XML-compliant document. The invocation
|
||||
`cat feed.xml feed2.xml | ytfeedmerge`, for example, assuming the given XML
|
||||
files are YouTube RSS feeds, will output a single YouTube RSS feed containing
|
||||
the entries from both feeds.
|
||||
|
||||
ytfeed.dl(1) is a tool to download a YouTube RSS feed using curl(1) or wget(1)
|
||||
and store it in the location used by ytfeed(6).
|
||||
|
||||
The Python programs are especially short, exclusively use Python's standard
|
||||
library, and are written to (hopefully) work on a reasonably recent Python 3
|
||||
interpreter. Unfortunately Python will probably shit itself anyway when you try
|
||||
to run them. For what it's worth, they're Python programs because most Linux
|
||||
distributions come with Python 3 and it's one of few languages with XML parsing
|
||||
as part of the standard library - I figured it'd be less hassle than vendoring
|
||||
a dependency or (woe be unto those who think it) relying on whatever awful
|
||||
package managers the user has installed.
|
||||
44
examples/feeds/UC4QobU6STFB0P71PMvOGN5A.xml
Normal file
44
examples/feeds/UC4QobU6STFB0P71PMvOGN5A.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns:yt="http://www.youtube.com/xml/schemas/2015" xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom">
|
||||
<link rel="self" href="http://www.youtube.com/feeds/videos.xml?channel_id=UC4QobU6STFB0P71PMvOGN5A"/>
|
||||
<id>yt:channel:4QobU6STFB0P71PMvOGN5A</id>
|
||||
<yt:channelId>4QobU6STFB0P71PMvOGN5A</yt:channelId>
|
||||
<title>jawed</title>
|
||||
<link rel="alternate" href="https://www.youtube.com/channel/UC4QobU6STFB0P71PMvOGN5A"/>
|
||||
<author>
|
||||
<name>jawed</name>
|
||||
<uri>https://www.youtube.com/channel/UC4QobU6STFB0P71PMvOGN5A</uri>
|
||||
</author>
|
||||
<published>2005-04-24T03:20:54+00:00</published>
|
||||
<entry>
|
||||
<id>yt:video:jNQXAC9IVRw</id>
|
||||
<yt:videoId>jNQXAC9IVRw</yt:videoId>
|
||||
<yt:channelId>UC4QobU6STFB0P71PMvOGN5A</yt:channelId>
|
||||
<title>Me at the zoo</title>
|
||||
<link rel="alternate" href="https://www.youtube.com/watch?v=jNQXAC9IVRw"/>
|
||||
<author>
|
||||
<name>jawed</name>
|
||||
<uri>https://www.youtube.com/channel/UC4QobU6STFB0P71PMvOGN5A</uri>
|
||||
</author>
|
||||
<published>2005-04-24T03:31:52+00:00</published>
|
||||
<updated>2025-08-22T10:26:34+00:00</updated>
|
||||
<media:group>
|
||||
<media:title>Me at the zoo</media:title>
|
||||
<media:content url="https://www.youtube.com/v/jNQXAC9IVRw?version=3" type="application/x-shockwave-flash" width="640" height="390"/>
|
||||
<media:thumbnail url="https://i3.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg" width="480" height="360"/>
|
||||
<media:description>Microplastics are accumulating in human brains at an alarming rate
|
||||
https://www.youtube.com/watch?v=0PT5c1z3LL8
|
||||
|
||||
“Nanoplastics and Human Health” with Matthew J Campen, PhD, MSPH
|
||||
https://www.youtube.com/watch?v=RRBN_4L09Mg
|
||||
|
||||
00:00 Intro
|
||||
00:05 The cool thing
|
||||
00:17 End</media:description>
|
||||
<media:community>
|
||||
<media:starRating count="18205539" average="5.00" min="1" max="5"/>
|
||||
<media:statistics views="371276149"/>
|
||||
</media:community>
|
||||
</media:group>
|
||||
</entry>
|
||||
</feed>
|
||||
47
examples/ytfeed
Executable file
47
examples/ytfeed
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
|
||||
# AGPLv3
|
||||
|
||||
test -n "$ytfeed_feeds_dir" || ytfeed_feeds_dir=feeds/
|
||||
export ytfeed_feeds_dir
|
||||
|
||||
printf "\
|
||||
____ __ __ __
|
||||
/_/ / /_ /_/ /_/ _/ /\\ ytfeed 2.0.0-pre
|
||||
__/ / / /_ /_ /_/ /__\\ dtb 2020-2025
|
||||
"
|
||||
|
||||
../menu <<EOF
|
||||
|
||||
Subscribe to new feed.
|
||||
|
||||
printf 'Please enter the channel ID to which to be subscribed: '
|
||||
</dev/tty \
|
||||
head -n 1 \
|
||||
| xargs ytfeed.dl "$ytfeed_feeds_dir"
|
||||
|
||||
Browse feeds.
|
||||
|
||||
ytfeed.browse-feeds "$ytfeed_feeds_dir" \
|
||||
| ../menu
|
||||
|
||||
Browse all feeds.
|
||||
|
||||
cat "$ytfeed_feeds_dir"/*.xml \
|
||||
| ytfeed.aggregate \
|
||||
| ytfeed.browse-feed \
|
||||
| ../menu
|
||||
|
||||
Refresh feeds.
|
||||
|
||||
for f in "$ytfeed_feeds_dir"/*.xml
|
||||
do printf '%s\\n' "\$f" \
|
||||
| sed -e 's,^.*/,,' \
|
||||
-e 's,\\.xml\$,,' \
|
||||
| xargs ytfeed.dl "$ytfeed_feeds_dir"
|
||||
done
|
||||
|
||||
Show configuration
|
||||
|
||||
printf 'ytfeed_feeds_dir: %s\n' "$ytfeed_feeds_dir" >&2
|
||||
EOF
|
||||
26
examples/ytfeed.aggregate
Executable file
26
examples/ytfeed.aggregate
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
# 2025 dtb. public domain
|
||||
import re, sys, xml.etree.ElementTree as ET
|
||||
ET.register_namespace("", "http://www.w3.org/2005/Atom")
|
||||
ET.register_namespace("media", "http://search.yahoo.com/mrss/")
|
||||
ET.register_namespace("yt", "http://www.youtube.com/xml/schemas/2015")
|
||||
def sortby(entry):
|
||||
return entry.findall("{http://www.w3.org/2005/Atom}published")[0].text
|
||||
macrofeed = ET.Element('feed')
|
||||
macrofeed.extend(
|
||||
sorted([
|
||||
entry
|
||||
# Split the input by file.
|
||||
for feed in re.split(r'<\?xml.*?\?>', sys.stdin.read())[1:]
|
||||
# Get the entries out of each file.
|
||||
for entry
|
||||
in ET.fromstring(feed)
|
||||
.findall("{http://www.w3.org/2005/Atom}entry")
|
||||
],
|
||||
# Gets the publication date and sorts the entries from old to new.
|
||||
key = lambda entry
|
||||
: entry.findall("{http://www.w3.org/2005/Atom}published")[0].text,
|
||||
reverse = False # toggle this to switch the order
|
||||
)
|
||||
)
|
||||
print(ET.tostring(macrofeed, encoding="unicode"))
|
||||
37
examples/ytfeed.browse-entry
Executable file
37
examples/ytfeed.browse-entry
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# 2025 dtb. public domain
|
||||
import sys, xml.etree.ElementTree as ET
|
||||
def f(fmt, e, t):
|
||||
r = e.find(t, {
|
||||
"": "http://www.w3.org/2005/Atom",
|
||||
"media": "http://search.yahoo.com/mrss/",
|
||||
"yt": "http://www.youtube.com/xml/schemas/2015"
|
||||
})
|
||||
return fmt % r.text if r is not None else ""
|
||||
tree = ET.parse(sys.stdin)
|
||||
root = tree.getroot()
|
||||
ytid = f("%s", root, "yt:videoId")
|
||||
print(
|
||||
f("[%s] ", root, "published")
|
||||
+ f("%s - ", root, "author/name")
|
||||
+ f("%s", root, "title")
|
||||
+ "\n\n"
|
||||
+ f("%s", root, "media:group/media:description"),
|
||||
end = "\n\n",
|
||||
file = sys.stderr
|
||||
)
|
||||
print(
|
||||
"#!/usr/bin/env menu" + "\n\n"
|
||||
+ "Download feed." + "\n\n"
|
||||
+ "\t" + "yt-dlp %s" % ytid + "\n\n"
|
||||
+ "Print feed URL." + "\n\n"
|
||||
+ "\t" + "printf 'https://youtube.com/watch?v=%%s\\n' '%s'" % ytid
|
||||
+ "\n\n"
|
||||
+ "Open feed URL in mpv." + "\n\n"
|
||||
# + "\t" + "mpv 'https://youtube.com/watch?v=%s'" % ytid + "\n\n"
|
||||
+ "\t" + "mpv" + " \\\n"
|
||||
+ "\t\t" + "--gpu-sw --profile=fast" + " \\\n"
|
||||
+ "\t\t" + "--script-opts=ytdl_hook-all_formats=yes" + " \\\n"
|
||||
+ "\t\t" + "'https://youtube.com/watch?v=%s'" % ytid,
|
||||
end = "\n\n"
|
||||
)
|
||||
28
examples/ytfeed.browse-feed
Executable file
28
examples/ytfeed.browse-feed
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
# 2025 dtb. public domain
|
||||
import sys, xml.etree.ElementTree as ET
|
||||
tree = ET.parse(sys.stdin)
|
||||
root = tree.getroot()
|
||||
def f(fmt, e, t, fn=None):
|
||||
r = e.find(t, {
|
||||
"": "http://www.w3.org/2005/Atom",
|
||||
"media": "http://search.yahoo.com/mrss/",
|
||||
"yt": "http://www.youtube.com/xml/schemas/2015}videoId"
|
||||
})
|
||||
r = r.text if r is not None else ""
|
||||
if fn is not None: r = fn(r)
|
||||
return fmt % r
|
||||
print("#!/usr/bin/env menu", end = "\n\n") # lead-in
|
||||
for entry in root.findall("{http://www.w3.org/2005/Atom}entry"):
|
||||
print(
|
||||
# Text
|
||||
f("[%s] ", entry, "published", fn = lambda s : s.split("T")[0])
|
||||
+ f("%-22s - ", entry, "author/name")
|
||||
+ f("%s", entry, "title") + "\n\n"
|
||||
# Command
|
||||
+ "\t" + "ytfeed.browse-entry <<EOF | ../menu" + "\n"
|
||||
+ "\t" + ET.tostring(entry, encoding="unicode")
|
||||
.replace("\n", "\n\t").rstrip() + "\n"
|
||||
+ "\tEOF",
|
||||
end = "\n\n"
|
||||
)
|
||||
22
examples/ytfeed.browse-feeds
Executable file
22
examples/ytfeed.browse-feeds
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
# 2025 dtb. public domain
|
||||
import os, sys, xml.etree.ElementTree as ET
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: %s directory\n" % sys.argv[0], file = sys.stderr)
|
||||
sys.exit(64) # sysexits(3) EX_USAGE
|
||||
channels = []
|
||||
directory = sys.argv[1]
|
||||
for f in os.listdir(directory):
|
||||
file_name = os.path.join(directory, f)
|
||||
if os.path.isfile(file_name) and f[-4:] == ".xml":
|
||||
tree = ET.parse(file_name)
|
||||
root = tree.getroot()
|
||||
try:
|
||||
channels += [
|
||||
"%s\n\n" % root.find('{http://www.w3.org/2005/Atom}title').text
|
||||
+ "\t<'%s' ytfeed.browse-feed | ../menu\n\n" % file_name
|
||||
]
|
||||
except: pass
|
||||
print("#!/usr/bin/env menu", end = "\n\n")
|
||||
for s in sorted(channels, key=str.lower): # Sorts alphabetically (caseless).
|
||||
print(s, end = "")
|
||||
21
examples/ytfeed.dl
Executable file
21
examples/ytfeed.dl
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# 2025 dtb. public domain
|
||||
directory="$1"
|
||||
alias have='command -v >/dev/null 2>&1'
|
||||
xml_url_prefix='https://www.youtube.com/feeds/videos.xml?channel_id='
|
||||
if test -z "$2"; then
|
||||
printf 'Usage: %s directory channel_id...\n' "$0" >&2
|
||||
exit 64 # sysexits(3) EX_USAGE
|
||||
fi
|
||||
while test -n "$2"; do
|
||||
if have curl; then curl=curl
|
||||
elif have wget; then curl='wget -O -'
|
||||
else curl=false
|
||||
fi
|
||||
filename="$(printf '%s/%s.xml\n' "$directory" "$2")"
|
||||
if ! $curl "$xml_url_prefix""$2" >"$filename"
|
||||
then rm -f "$filename"
|
||||
else printf '%s\n' "$filename"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
Reference in New Issue
Block a user