rssViedos mostly
This commit is contained in:
parent
ceb1e40964
commit
709239dfca
|
@ -86,7 +86,7 @@
|
||||||
|
|
||||||
- name: Download base16 theme for qutebrowser
|
- name: Download base16 theme for qutebrowser
|
||||||
get_url:
|
get_url:
|
||||||
url: "https://raw.githubusercontent.com/theova/base16-qutebrowser/master/themes/default/base16-{{ base16_scheme }}.config.py"
|
url: "https://raw.githubusercontent.com/theova/base16-qutebrowser/main/themes/default/base16-{{ base16_scheme }}.config.py"
|
||||||
dest: "{{ ansible_env.HOME }}/.config/qutebrowser/theme.py"
|
dest: "{{ ansible_env.HOME }}/.config/qutebrowser/theme.py"
|
||||||
mode: "u+rw,g=r,o=r"
|
mode: "u+rw,g=r,o=r"
|
||||||
notify:
|
notify:
|
||||||
|
@ -111,8 +111,7 @@
|
||||||
dest: "{{ ansible_env.HOME }}/.config/dunst/dunstrc"
|
dest: "{{ ansible_env.HOME }}/.config/dunst/dunstrc"
|
||||||
mode: "u+rw,g=r,o=r"
|
mode: "u+rw,g=r,o=r"
|
||||||
|
|
||||||
# TODO mechanism to switching light/dark quickly
|
# TODO Generate themes locally because not being able to switch themes because GitHub being down is just silly
|
||||||
# TODO dunst (template online, but not to my liking)
|
|
||||||
# TODO bar (might change bar in the future, so...)
|
# TODO bar (might change bar in the future, so...)
|
||||||
# TODO highlight (there IS a template but the colors look different from vim and mostly the same from when there's no config)
|
# TODO highlight (there IS a template but the colors look different from vim and mostly the same from when there's no config)
|
||||||
# TODO https://github.com/makuto/auto-base16-theme ? :P
|
# TODO https://github.com/makuto/auto-base16-theme ? :P
|
||||||
|
|
1
config/ccache.conf
Normal file
1
config/ccache.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ccache_dir = $HOME/.cache/ccache
|
3
config/flake8
Normal file
3
config/flake8
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[flake8]
|
||||||
|
# Compatibility with Black
|
||||||
|
max-line-length = 88
|
|
@ -381,7 +381,7 @@ exec --no-startup-id keynav # Keyboard cursor controller
|
||||||
# exec --no-startup-id ~/.config/i3/ashuffle # MPD Auto-refill
|
# exec --no-startup-id ~/.config/i3/ashuffle # MPD Auto-refill
|
||||||
exec --no-startup-id autorandr --change --force # Screen configuration and everything that depends on it
|
exec --no-startup-id autorandr --change --force # Screen configuration and everything that depends on it
|
||||||
exec --no-startup-id ~/.config/i3/batteryNotify -d # Battery state notification
|
exec --no-startup-id ~/.config/i3/batteryNotify -d # Battery state notification
|
||||||
exec --no-startup-id ~/.config/i3/aw_start # Activity tracker
|
# exec --no-startup-id ~/.config/i3/aw_start # Activity tracker
|
||||||
|
|
||||||
|
|
||||||
{{ lookup('file', ansible_env.HOME + '/.config/i3/theme') }}
|
{{ lookup('file', ansible_env.HOME + '/.config/i3/theme') }}
|
||||||
|
@ -389,7 +389,7 @@ set $ignore #ff00ff
|
||||||
|
|
||||||
# Basic color configuration using the Base16 variables for windows and borders.
|
# Basic color configuration using the Base16 variables for windows and borders.
|
||||||
# Property Name Border BG Text Indicator Child Border
|
# Property Name Border BG Text Indicator Child Border
|
||||||
client.focused $base00 $base00 $base05 $base00 $base07
|
client.focused $base0B $base0B $base00 $base00 $base0B
|
||||||
client.focused_inactive $base02 $base02 $base05 $base02 $base02
|
client.focused_inactive $base02 $base02 $base05 $base02 $base02
|
||||||
client.unfocused $base05 $base04 $base00 $base04 $base00
|
client.unfocused $base05 $base04 $base00 $base04 $base00
|
||||||
client.urgent $base0F $base08 $base00 $base08 $base0F
|
client.urgent $base0F $base08 $base00 $base08 $base0F
|
||||||
|
|
3
config/pycodestyle
Normal file
3
config/pycodestyle
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[pycodestyle]
|
||||||
|
# Compatibility with Black
|
||||||
|
max-line-length = 88
|
8
config/scripts/crepuscule
Executable file
8
config/scripts/crepuscule
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# TODO De-hardcode
|
||||||
|
|
||||||
|
cd ~/.dotfiles/config/automatrop
|
||||||
|
echo 30000 | sudo tee /sys/class/backlight/intel_backlight/brightness
|
||||||
|
xrandr --output HDMI-0 --brightness 1
|
||||||
|
ansible-playbook playbooks/default.yml -e base16_scheme=solarized-dark
|
8
config/scripts/jour
Executable file
8
config/scripts/jour
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# TODO De-hardcode
|
||||||
|
|
||||||
|
cd ~/.dotfiles/config/automatrop
|
||||||
|
echo 30000 | sudo tee /sys/class/backlight/intel_backlight/brightness
|
||||||
|
xrandr --output HDMI-0 --brightness 1
|
||||||
|
ansible-playbook playbooks/default.yml -e base16_scheme=solarized-light
|
58
config/scripts/mediaDuration
Executable file
58
config/scripts/mediaDuration
Executable file
|
@ -0,0 +1,58 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
|
||||||
|
coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s')
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
def duration_file(path: str) -> float:
|
||||||
|
cmd = [
|
||||||
|
"ffprobe",
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-show_entries",
|
||||||
|
"format=duration",
|
||||||
|
"-of",
|
||||||
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
path,
|
||||||
|
]
|
||||||
|
run = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||||
|
ret = run.stdout.decode().strip()
|
||||||
|
if run.returncode != 0:
|
||||||
|
log.warning(f"{path}: unable to get duration")
|
||||||
|
elif ret == 'N/A':
|
||||||
|
log.warning(f"{path}: has no duration")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return float(ret)
|
||||||
|
except ValueError:
|
||||||
|
log.error(f"{path}: returned {ret}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def duration_directory(path: str) -> float:
|
||||||
|
total = 0.0
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
for f in files:
|
||||||
|
fullPath = os.path.join(root, f)
|
||||||
|
total += duration_file(fullPath)
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
total = 0.0
|
||||||
|
for arg in sys.argv[1:]:
|
||||||
|
if os.path.isfile(arg):
|
||||||
|
total += duration_file(arg)
|
||||||
|
elif os.path.isdir(arg):
|
||||||
|
total += duration_directory(arg)
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError(f"No such file or directory: '{arg}'")
|
||||||
|
|
||||||
|
|
||||||
|
print(total)
|
8
config/scripts/nuit
Executable file
8
config/scripts/nuit
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# TODO De-hardcode
|
||||||
|
|
||||||
|
cd ~/.dotfiles/config/automatrop
|
||||||
|
echo 1 | sudo tee /sys/class/backlight/intel_backlight/brightness
|
||||||
|
xrandr --output HDMI-0 --brightness 0.5
|
||||||
|
ansible-playbook playbooks/default.yml -e base16_scheme=solarized-dark
|
|
@ -10,10 +10,10 @@ with the unread items (non-video links are ignored).
|
||||||
# TODO Distribute this correclty, in the meanwhile please do
|
# TODO Distribute this correclty, in the meanwhile please do
|
||||||
# pip install --user youtube-dl ConfigArgParse progressbar2
|
# pip install --user youtube-dl ConfigArgParse progressbar2
|
||||||
|
|
||||||
# TODO Allow to specify youtube_dl options (e.g. subtitles)
|
# TODO Better logging (youtube-dl allow to pass loggers)
|
||||||
# TODO Restrict quality (it's not that I don't like 8GB 4K videos but...)
|
|
||||||
|
|
||||||
from typing import Dict, Set
|
import sys
|
||||||
|
from typing import Dict, Set, Tuple
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import os
|
import os
|
||||||
|
@ -22,27 +22,69 @@ import youtube_dl
|
||||||
import configargparse
|
import configargparse
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def get_args() -> configargparse.Namespace:
|
||||||
|
defaultConfigPath = os.path.join(
|
||||||
|
os.path.expanduser(os.getenv("XDG_CONFIG_PATH", "~/.config/")), "rssVideos"
|
||||||
|
)
|
||||||
|
|
||||||
defaultConfigPath = os.path.join(os.path.expanduser(
|
parser = configargparse.ArgParser(
|
||||||
os.getenv('XDG_CONFIG_PATH', '~/.config/')), 'rssVideos')
|
description="Download videos linked in "
|
||||||
|
+ "a RSS feed (e.g. an unread feed from "
|
||||||
|
+ "an RSS aggregator",
|
||||||
parser = configargparse.ArgParser(description="Download videos linked in " +
|
default_config_files=[defaultConfigPath],
|
||||||
"a RSS feed (e.g. an unread feed from " +
|
)
|
||||||
"an RSS aggregator",
|
parser.add(
|
||||||
default_config_files=[defaultConfigPath])
|
"-c", "--config", required=False, is_config_file=True, help="Configuration file"
|
||||||
parser.add('-c', '--config', required=False, is_config_file=True,
|
)
|
||||||
help='Configuration file')
|
parser.add(
|
||||||
parser.add('--feed', help='URL of the RSS feed (must be public for now)',
|
"--feed",
|
||||||
env_var='RSS_VIDEOS_FEED', required=True)
|
help="URL of the RSS feed (must be public for now)",
|
||||||
parser.add('--videos', help='Directory to store videos',
|
env_var="RSS_VIDEOS_FEED",
|
||||||
env_var='RSS_VIDEOS_VIDEO_DIR', required=True)
|
required=True,
|
||||||
parser.add('-n', '--dryrun', help='Do not download the videos',
|
)
|
||||||
action='store_const', const=True, default=False)
|
parser.add(
|
||||||
# TODO This feature might require additional documentation and an on/off switc
|
"--videos",
|
||||||
parser.add('--track', help='Directory where download videos are maked (so they are not downloaded twice)',
|
help="Directory to store videos",
|
||||||
env_var='RSS_VIDEOS_TRACK', required=False, default='.rssVideos')
|
env_var="RSS_VIDEOS_VIDEO_DIR",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add(
|
||||||
|
"-n",
|
||||||
|
"--dryrun",
|
||||||
|
help="Do not download the videos",
|
||||||
|
action="store_const",
|
||||||
|
const=True,
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
# TODO This feature might require additional documentation and an on/off switch
|
||||||
|
parser.add(
|
||||||
|
"--track",
|
||||||
|
help="Directory where download videos are marked "
|
||||||
|
+ "to not download them after deletion.",
|
||||||
|
env_var="RSS_VIDEOS_TRACK",
|
||||||
|
required=False,
|
||||||
|
default=".rssVideos",
|
||||||
|
)
|
||||||
|
parser.add(
|
||||||
|
"--max-duration",
|
||||||
|
help="Skip video longer than this amount of seconds",
|
||||||
|
env_var="RSS_VIDEOS_MAX_DURATION",
|
||||||
|
type=int,
|
||||||
|
default=0,
|
||||||
|
)
|
||||||
|
parser.add(
|
||||||
|
"--format",
|
||||||
|
help="Use this format to download videos."
|
||||||
|
+ " See FORMAT SELECTION in youtube-dl(1)",
|
||||||
|
env_var="RSS_VIDEOS_FORMAT",
|
||||||
|
default="bestvideo+bestaudio/best",
|
||||||
|
)
|
||||||
|
parser.add(
|
||||||
|
"--subtitles",
|
||||||
|
help="Download all subtitles",
|
||||||
|
env_var="RSS_VIDEOS_SUBTITLES",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
args.videos = os.path.realpath(os.path.expanduser(args.videos))
|
args.videos = os.path.realpath(os.path.expanduser(args.videos))
|
||||||
|
@ -50,54 +92,70 @@ if __name__ == "__main__":
|
||||||
if not os.path.isabs(args.track):
|
if not os.path.isabs(args.track):
|
||||||
args.track = os.path.realpath(os.path.join(args.videos, args.track))
|
args.track = os.path.realpath(os.path.join(args.videos, args.track))
|
||||||
|
|
||||||
os.makedirs(args.videos, exist_ok=True)
|
return args
|
||||||
os.makedirs(args.track, exist_ok=True)
|
|
||||||
|
|
||||||
# Read the feed XML, get the links
|
|
||||||
print("→ Retrieveing RSS feed")
|
|
||||||
|
|
||||||
links: Set[str] = set()
|
def get_links(args: configargparse.Namespace) -> Set[str]:
|
||||||
|
"""
|
||||||
|
Read the feed XML, get the links
|
||||||
|
"""
|
||||||
|
links = set()
|
||||||
with urllib.request.urlopen(args.feed) as request:
|
with urllib.request.urlopen(args.feed) as request:
|
||||||
with minidom.parse(request) as xmldoc:
|
with minidom.parse(request) as xmldoc:
|
||||||
for item in xmldoc.getElementsByTagName('item'):
|
for item in xmldoc.getElementsByTagName("item"):
|
||||||
try:
|
try:
|
||||||
linkNode = item.getElementsByTagName('link')[0]
|
linkNode = item.getElementsByTagName("link")[0]
|
||||||
link: str = linkNode.childNodes[0].data
|
link: str = linkNode.childNodes[0].data
|
||||||
links.add(link)
|
links.add(link)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
print("Error while getting link from item:", e)
|
print("Error while getting link from item:", e)
|
||||||
continue
|
continue
|
||||||
|
return links
|
||||||
|
|
||||||
# Filter out non-video links and store video download info
|
|
||||||
# and associated filename
|
|
||||||
print(f"→ Getting infos on {len(links)} unread articles")
|
|
||||||
|
|
||||||
videosInfos: Dict[str, str] = {}
|
def get_video_infos(
|
||||||
|
args: configargparse.Namespace, ydl_opts: Dict, links: Set[str]
|
||||||
|
) -> Dict[str, Dict]:
|
||||||
|
"""
|
||||||
|
Filter out non-video links and store video download info
|
||||||
|
and associated filename
|
||||||
|
"""
|
||||||
|
videosInfos = dict()
|
||||||
|
|
||||||
ydl_opts = {
|
dry_ydl_opts = ydl_opts.copy()
|
||||||
"simulate": True,
|
dry_ydl_opts.update({"simulate": True, "quiet": True})
|
||||||
"quiet": True
|
with youtube_dl.YoutubeDL(dry_ydl_opts) as ydl:
|
||||||
}
|
|
||||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
|
||||||
for link in links:
|
for link in links:
|
||||||
print(f"Researching {link}...")
|
print(f"Researching {link}...")
|
||||||
try:
|
try:
|
||||||
infos = ydl.extract_info(link)
|
infos = ydl.extract_info(link)
|
||||||
|
if args.max_duration > 0 and infos["duration"] > args.max_duration:
|
||||||
|
print(
|
||||||
|
f"{infos['title']}: Skipping as longer than max duration: "
|
||||||
|
f"{infos['duration']} > {args.max_duration}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
filepath = ydl.prepare_filename(infos)
|
filepath = ydl.prepare_filename(infos)
|
||||||
filename, extension = os.path.splitext(filepath)
|
filename, extension = os.path.splitext(filepath)
|
||||||
videosInfos[filename] = infos
|
videosInfos[filename] = infos
|
||||||
|
print(f"{infos['title']}: Added")
|
||||||
|
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
print(e)
|
print(e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Read the directory content, delete everything that's not a
|
return videosInfos
|
||||||
# video on the download list or already downloaded
|
|
||||||
print(f"→ Deciding on what to do for {len(videosInfos)} videos")
|
|
||||||
|
|
||||||
# Getting information on the video directory
|
|
||||||
|
|
||||||
videosDownloaded: Set[str] = set()
|
def get_downloaded_videos(
|
||||||
videosPartiallyDownloaded: Set[str] = set()
|
args: configargparse.Namespace, videosInfos: Dict[str, Dict]
|
||||||
|
) -> Tuple[Set[str], Set[str]]:
|
||||||
|
videosDownloaded = set()
|
||||||
|
videosPartiallyDownloaded = set()
|
||||||
|
"""
|
||||||
|
Read the directory content, delete everything that's not a
|
||||||
|
video on the download list or already downloaded
|
||||||
|
"""
|
||||||
|
|
||||||
for filepath in os.listdir(args.videos):
|
for filepath in os.listdir(args.videos):
|
||||||
fullpath = os.path.join(args.videos, filepath)
|
fullpath = os.path.join(args.videos, filepath)
|
||||||
|
@ -106,12 +164,19 @@ if __name__ == "__main__":
|
||||||
filename, extension = os.path.splitext(filepath)
|
filename, extension = os.path.splitext(filepath)
|
||||||
|
|
||||||
for onlineFilename in videosInfos.keys():
|
for onlineFilename in videosInfos.keys():
|
||||||
# Full name already there: completly downloaded → remove from the download list
|
# Full name already there: completly downloaded
|
||||||
|
# → remove from the download list
|
||||||
if filename == onlineFilename:
|
if filename == onlineFilename:
|
||||||
videosDownloaded.add(onlineFilename)
|
videosDownloaded.add(onlineFilename)
|
||||||
break
|
break
|
||||||
# Partial name already there: not completly downloaded → keep on the download list
|
|
||||||
elif filename.startswith(onlineFilename):
|
elif filename.startswith(onlineFilename):
|
||||||
|
# Subtitle file
|
||||||
|
# → ignore
|
||||||
|
if filename.endswith(".vtt"):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Partial name already there: not completly downloaded
|
||||||
|
# → keep on the download list
|
||||||
videosPartiallyDownloaded.add(onlineFilename)
|
videosPartiallyDownloaded.add(onlineFilename)
|
||||||
break
|
break
|
||||||
# Unrelated filename: delete
|
# Unrelated filename: delete
|
||||||
|
@ -119,10 +184,17 @@ if __name__ == "__main__":
|
||||||
print(f"Deleting: {filename}")
|
print(f"Deleting: {filename}")
|
||||||
os.unlink(fullpath)
|
os.unlink(fullpath)
|
||||||
|
|
||||||
# Getting informations on the tracking directory
|
return videosDownloaded, videosPartiallyDownloaded
|
||||||
|
|
||||||
# Videos that were once downloaded using this tool
|
|
||||||
videosTracked: Set[str] = set()
|
def get_tracked_videos(args: configargparse.Namespace, known: Set[str]) -> Set[str]:
|
||||||
|
"""
|
||||||
|
Return videos previously downloaded (=tracked) amongst the unread videos.
|
||||||
|
This is stored in the tracking directory as empty extension-less files.
|
||||||
|
Other tracking markers (e.g. for now read videos) are deleted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
videosTracked = set()
|
||||||
|
|
||||||
for filepath in os.listdir(args.track):
|
for filepath in os.listdir(args.track):
|
||||||
fullpath = os.path.join(args.track, filepath)
|
fullpath = os.path.join(args.track, filepath)
|
||||||
|
@ -130,18 +202,39 @@ if __name__ == "__main__":
|
||||||
continue
|
continue
|
||||||
# Here filename is a filepath as no extension
|
# Here filename is a filepath as no extension
|
||||||
|
|
||||||
if filepath in videosInfos:
|
if filepath in known:
|
||||||
videosTracked.add(filepath)
|
videosTracked.add(filepath)
|
||||||
else:
|
else:
|
||||||
os.unlink(fullpath)
|
os.unlink(fullpath)
|
||||||
|
|
||||||
|
return videosTracked
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
|
||||||
|
args = get_args()
|
||||||
|
|
||||||
|
os.makedirs(args.videos, exist_ok=True)
|
||||||
|
os.makedirs(args.track, exist_ok=True)
|
||||||
|
ydl_opts = {"format": args.format, "allsubtitles": args.subtitles}
|
||||||
|
|
||||||
|
print("→ Retrieveing RSS feed")
|
||||||
|
links = get_links(args)
|
||||||
|
|
||||||
|
print(f"→ Getting infos on {len(links)} unread articles")
|
||||||
|
videosInfos = get_video_infos(args, ydl_opts, links)
|
||||||
|
|
||||||
|
print(f"→ Deciding on what to do for {len(videosInfos)} videos")
|
||||||
|
videosDownloaded, videosPartiallyDownloaded = get_downloaded_videos(
|
||||||
|
args, videosInfos
|
||||||
|
)
|
||||||
|
videosTracked = get_tracked_videos(args, set(videosInfos.keys()))
|
||||||
|
|
||||||
# Deciding for the rest based on the informations
|
# Deciding for the rest based on the informations
|
||||||
|
|
||||||
|
def markTracked(filename: str) -> None:
|
||||||
def markTracked(filename):
|
|
||||||
markerPath = os.path.join(args.track, onlineFilename)
|
markerPath = os.path.join(args.track, onlineFilename)
|
||||||
open(markerPath, 'a').close()
|
open(markerPath, "a").close()
|
||||||
|
|
||||||
|
|
||||||
videosToDownload: Set[str] = set()
|
videosToDownload: Set[str] = set()
|
||||||
videosReads: Set[str] = set()
|
videosReads: Set[str] = set()
|
||||||
|
@ -169,11 +262,10 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
os.chdir(args.videos)
|
os.chdir(args.videos)
|
||||||
|
|
||||||
|
exit_code = 0
|
||||||
if not args.dryrun:
|
if not args.dryrun:
|
||||||
# TODO Progressbar one day maybe?
|
# TODO Progressbar one day maybe?
|
||||||
# We have all the info we need to make a reliable one
|
# We have all the info we need to make a reliable one
|
||||||
ydl_opts = {
|
|
||||||
}
|
|
||||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||||
for onlineFilename in videosToDownload:
|
for onlineFilename in videosToDownload:
|
||||||
infos = videosInfos[onlineFilename]
|
infos = videosInfos[onlineFilename]
|
||||||
|
@ -183,6 +275,13 @@ if __name__ == "__main__":
|
||||||
ydl.process_ie_result(infos, True, {})
|
ydl.process_ie_result(infos, True, {})
|
||||||
|
|
||||||
markTracked(onlineFilename)
|
markTracked(onlineFilename)
|
||||||
except:
|
except BaseException as e:
|
||||||
|
print(e)
|
||||||
|
exit_code = 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
|
@ -1,43 +1,74 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse
|
import base64
|
||||||
|
import colorama
|
||||||
|
import configargparse
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
|
||||||
import email.utils
|
import email.utils
|
||||||
import subprocess
|
import io
|
||||||
import pprint
|
import pprint
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
def command(command: str) -> None:
|
|
||||||
cmd = command.encode() + b"\n"
|
|
||||||
subprocess.run(["xdotool", "type", "--file", "-"], input=cmd)
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
parser = configargparse.ArgParser(
|
||||||
description="Generate SMTP messages to send a mail"
|
description="Generate SMTP messages to send a mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
now_email = email.utils.formatdate(now.timestamp(), True)
|
now_email = email.utils.formatdate(now.timestamp(), True)
|
||||||
|
|
||||||
parser.add_argument("-s", "--sender", default="geoffrey@frogeye.fr")
|
parser.add_argument("-o", "--origin", env_var="ORIGIN", default="localhost")
|
||||||
parser.add_argument("-r", "--receiver", default="geoffrey@frogeye.fr")
|
|
||||||
parser.add_argument("-l", "--helo", default="frogeye.fr")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-o", "--subject", default=f"Test message {now.strftime('%H:%M:%S')}"
|
"-d", "--destination", env_var="DESTINATION", default="localhost"
|
||||||
|
)
|
||||||
|
parser.add_argument("-p", "--port", env_var="PORT", default=25)
|
||||||
|
parser.add_argument(
|
||||||
|
"-S", "--security", env_var="SECURITY", choices=["plain", "ssl", "starttls"], default='plain'
|
||||||
)
|
)
|
||||||
parser.add_argument("-m", "--me", default="Geoffrey")
|
|
||||||
parser.add_argument("-g", "--gtube", action="store_true")
|
|
||||||
parser.add_argument("-d", "--debug", action="store_true")
|
|
||||||
parser.add_argument("-b", "--body", default="")
|
|
||||||
|
|
||||||
|
parser.add_argument("-l", "--helo", env_var="HELO")
|
||||||
|
parser.add_argument(
|
||||||
|
"-s", "--sender", env_var="SENDER", default="geoffrey@frogeye.fr"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-r", "--receiver", env_var="RECEIVER", default="geoffrey@frogeye.fr"
|
||||||
|
)
|
||||||
|
# parser.add_argument("-a", "--auth", env_var="AUTH", default="PLAIN")
|
||||||
|
parser.add_argument("-u", "--user", env_var="MUSER")
|
||||||
|
parser.add_argument("-w", "--password", env_var="PASSWORD")
|
||||||
|
|
||||||
|
parser.add_argument("-f", "--from", env_var="FROM")
|
||||||
|
parser.add_argument("-t", "--to", env_var="TO")
|
||||||
|
parser.add_argument(
|
||||||
|
"-j",
|
||||||
|
"--subject",
|
||||||
|
env_var="SUBJECT",
|
||||||
|
default=f"Test message {now.strftime('%H:%M:%S')}",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("-b", "--body", env_var="BODY", default="")
|
||||||
|
parser.add_argument("-g", "--gtube", env_var="GTUBE", action="store_true")
|
||||||
|
parser.add_argument("-m", "--me", env_var="ME", default="Geoffrey")
|
||||||
|
|
||||||
|
parser.add_argument("-y", "--dryrun", env_var="DRYRUN", action="store_true")
|
||||||
|
parser.add_argument("-q", "--quiet", env_var="QUIET", action="store_true")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.debug:
|
# Default values
|
||||||
command = print
|
if args.helo is None:
|
||||||
|
args.helo = args.origin
|
||||||
|
if getattr(args, "from") is None:
|
||||||
|
setattr(args, "from", args.sender)
|
||||||
|
if args.to is None:
|
||||||
|
args.to = args.receiver
|
||||||
|
if args.password:
|
||||||
|
password = args.password
|
||||||
|
args.password = '********'
|
||||||
|
|
||||||
|
# Transmission content
|
||||||
|
|
||||||
gtube = ""
|
gtube = ""
|
||||||
if args.gtube:
|
if args.gtube:
|
||||||
|
@ -49,11 +80,10 @@ XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X"""
|
||||||
if args.body:
|
if args.body:
|
||||||
body = f"\n\n{args.body}"
|
body = f"\n\n{args.body}"
|
||||||
|
|
||||||
|
|
||||||
text = f"""Date: {now_email}
|
text = f"""Date: {now_email}
|
||||||
From: {args.sender}
|
From: {getattr(args, 'from')}
|
||||||
Subject: {args.subject}
|
Subject: {args.subject}
|
||||||
To: {args.receiver}
|
To: {args.to}
|
||||||
|
|
||||||
Hello there,
|
Hello there,
|
||||||
|
|
||||||
|
@ -63,28 +93,96 @@ If you didn't expect to see this message, please contact {args.me}.{gtube}{body}
|
||||||
Greetings,
|
Greetings,
|
||||||
|
|
||||||
Input arguments:
|
Input arguments:
|
||||||
{pprint.pformat(args, indent=4)}
|
{pprint.pformat(args.__dict__, indent=4)}
|
||||||
|
|
||||||
--
|
--
|
||||||
{args.me}
|
{args.me}
|
||||||
."""
|
."""
|
||||||
|
|
||||||
if not args.debug:
|
# Transmission setup
|
||||||
for i in range(3, 0, -1):
|
cmd = ["ssh", args.origin]
|
||||||
print(f"Typing mail in {i}…")
|
if args.security == "plain":
|
||||||
time.sleep(1)
|
cmd += ["socat", "-", f"tcp:{args.destination}:{args.port}"]
|
||||||
|
elif args.security == "ssl":
|
||||||
|
cmd += ["socat", "-", f"openssl:{args.destination}:{args.port}"]
|
||||||
|
elif args.security == "starttls":
|
||||||
|
cmd += [
|
||||||
|
"openssl",
|
||||||
|
"s_client",
|
||||||
|
"-starttls",
|
||||||
|
"smtp",
|
||||||
|
"-crlf",
|
||||||
|
"-connect",
|
||||||
|
f"{args.destination}:{args.port}",
|
||||||
|
"-quiet",
|
||||||
|
]
|
||||||
|
|
||||||
command(f"EHLO {args.helo}")
|
if not args.quiet:
|
||||||
command(f"MAIL FROM: <{args.sender}>")
|
print(colorama.Fore.MAGENTA + f"# {' '.join(cmd)}" + colorama.Fore.RESET)
|
||||||
command(f"RCPT TO: <{args.receiver}>")
|
|
||||||
command("DATA")
|
|
||||||
command(text)
|
|
||||||
command("QUIT")
|
|
||||||
|
|
||||||
print("Done")
|
if not args.dryrun:
|
||||||
|
p = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
def recv() -> None:
|
||||||
|
if args.dryrun:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert isinstance(p.stdout, io.BufferedReader)
|
||||||
|
next = True
|
||||||
|
while next:
|
||||||
|
line = p.stdout.readline()
|
||||||
|
code = int(line[:3])
|
||||||
|
success = code < 400
|
||||||
|
color = colorama.Fore.GREEN if success else colorama.Fore.RED
|
||||||
|
if not args.quiet:
|
||||||
|
print(color + f"< {line[:-1].decode()}" + colorama.Fore.RESET)
|
||||||
|
next = line[3] == b"-"[0]
|
||||||
|
if not next and not success:
|
||||||
|
send("QUIT") # TODO Can loop if QUIT fails
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def send(command: str) -> None:
|
||||||
|
if not args.quiet:
|
||||||
|
print(colorama.Fore.BLUE + f"> {command}" + colorama.Fore.RESET)
|
||||||
|
|
||||||
|
if args.dryrun:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert isinstance(p.stdin, io.BufferedWriter)
|
||||||
|
cmd = command.encode() + b"\n"
|
||||||
|
p.stdin.write(cmd)
|
||||||
|
p.stdin.flush()
|
||||||
|
|
||||||
|
recv()
|
||||||
|
|
||||||
|
# Transmission
|
||||||
|
|
||||||
|
if args.security != 'starttls':
|
||||||
|
recv()
|
||||||
|
send(f"EHLO {args.helo}")
|
||||||
|
if args.user:
|
||||||
|
encoded = base64.b64encode(
|
||||||
|
args.user.encode()
|
||||||
|
+ b"\x00"
|
||||||
|
+ args.user.encode()
|
||||||
|
+ b"\x00"
|
||||||
|
+ password.encode()
|
||||||
|
).decode()
|
||||||
|
send(f"AUTH PLAIN {encoded}")
|
||||||
|
send(f"MAIL FROM: <{args.sender}>")
|
||||||
|
send(f"RCPT TO: <{args.receiver}>")
|
||||||
|
send("DATA")
|
||||||
|
send(text)
|
||||||
|
send("QUIT")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# For reference:
|
# For reference:
|
||||||
# command("RSET")
|
# send("RSET")
|
||||||
# command("VRFY")
|
# send("VRFY")
|
||||||
# command("NOOP")
|
# send("NOOP")
|
||||||
# command("QUIT")
|
# send("QUIT")
|
||||||
|
|
55
config/scripts/videoQuota
Executable file
55
config/scripts/videoQuota
Executable file
|
@ -0,0 +1,55 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
|
||||||
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s")
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
def duration_file(path: str) -> float:
|
||||||
|
cmd = [
|
||||||
|
"ffprobe",
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-show_entries",
|
||||||
|
"format=duration",
|
||||||
|
"-of",
|
||||||
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
path,
|
||||||
|
]
|
||||||
|
run = subprocess.run(cmd, stdout=subprocess.PIPE, check=True)
|
||||||
|
ret = run.stdout.decode().strip()
|
||||||
|
return float(ret)
|
||||||
|
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
audio_br_bi = 128000
|
||||||
|
|
||||||
|
# TODO Arguments if you feel like it
|
||||||
|
quota_by = int(sys.argv[1])
|
||||||
|
in_file = sys.argv[2]
|
||||||
|
out_file = sys.argv[3]
|
||||||
|
|
||||||
|
quota_bi = quota_by * 8
|
||||||
|
duration = duration_file(in_file)
|
||||||
|
tot_br_bi = quota_bi / duration
|
||||||
|
video_br_bi = int(tot_br_bi - audio_br_bi)
|
||||||
|
assert video_br_bi > 0, "Not even enough space for audio"
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-i",
|
||||||
|
in_file,
|
||||||
|
"-b:v",
|
||||||
|
str(video_br_bi),
|
||||||
|
"-b:a",
|
||||||
|
str(audio_br_bi),
|
||||||
|
out_file,
|
||||||
|
]
|
||||||
|
|
||||||
|
subprocess.run(cmd, check=True)
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# change Paris to your default location
|
# change Paris to your default location
|
||||||
request="v2.wttr.in/$1"
|
request="v2.wttr.in/${1-Amsterdam}"
|
||||||
[ "$(tput cols)" -lt 125 ] && request+='?n'
|
[ "$(tput cols)" -lt 125 ] && request+='?n'
|
||||||
curl -H "Accept-Language: ${LANG%_*}" --compressed "$request"
|
curl -H "Accept-Language: ${LANG%_*}" --compressed "$request"
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,28 @@ FZF_DEFAULT_OPTS="--height 40% --layout=default"
|
||||||
FZF_CTRL_T_OPTS="--preview '[[ -d {} ]] && ls -l --color=always {} || [[ \$(file --mime {}) =~ binary ]] && file --brief {} || (highlight -O ansi -l {} || coderay {} || rougify {} || cat {}) 2> /dev/null | head -500'"
|
FZF_CTRL_T_OPTS="--preview '[[ -d {} ]] && ls -l --color=always {} || [[ \$(file --mime {}) =~ binary ]] && file --brief {} || (highlight -O ansi -l {} || coderay {} || rougify {} || cat {}) 2> /dev/null | head -500'"
|
||||||
FZF_COMPLETION_OPTS="${FZF_CTRL_T_OPTS}"
|
FZF_COMPLETION_OPTS="${FZF_CTRL_T_OPTS}"
|
||||||
|
|
||||||
|
# Colored ls
|
||||||
|
_colored_ls() {
|
||||||
|
\ls -lh --color=always $@ | awk '
|
||||||
|
BEGIN {
|
||||||
|
FPAT = "([[:space:]]*[^[:space:]]+)";
|
||||||
|
OFS = "";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
$1 = "\033[36m" $1 "\033[0m";
|
||||||
|
$2 = "\033[31m" $2 "\033[0m";
|
||||||
|
$3 = "\033[32m" $3 "\033[0m";
|
||||||
|
$4 = "\033[32m" $4 "\033[0m";
|
||||||
|
$5 = "\033[31m" $5 "\033[0m";
|
||||||
|
$6 = "\033[34m" $6 "\033[0m";
|
||||||
|
$7 = "\033[34m" $7 "\033[0m";
|
||||||
|
print
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
alias ll="_colored_ls"
|
||||||
|
alias la="_colored_ls -a"
|
||||||
|
|
||||||
## FUNCTIONS
|
## FUNCTIONS
|
||||||
|
|
||||||
## MISC
|
## MISC
|
||||||
|
|
|
@ -26,8 +26,8 @@ export JAVA_FONTS=/usr/share/fonts/TTF # 2019-04-25 Attempt to remove .java/font
|
||||||
# Get out of my $HOME!
|
# Get out of my $HOME!
|
||||||
export BOOT9_PATH="$HOME/.local/share/citra-emu/sysdata/boot9.bin"
|
export BOOT9_PATH="$HOME/.local/share/citra-emu/sysdata/boot9.bin"
|
||||||
direnv CARGOHOME "$HOME/.cache/cargo" # There are config in there that we can version if one want
|
direnv CARGOHOME "$HOME/.cache/cargo" # There are config in there that we can version if one want
|
||||||
direnv CCACHE_BASEDIR "$HOME/.cache/ccache"
|
|
||||||
export CCACHE_CONFIGPATH="$HOME/.config/ccache.conf"
|
export CCACHE_CONFIGPATH="$HOME/.config/ccache.conf"
|
||||||
|
direnv CCACHE_DIR "$HOME/.cache/ccache" # The config file alone seems to be not enough
|
||||||
direnv DASHT_DOCSETS_DIR "$HOME/.cache/dash_docsets"
|
direnv DASHT_DOCSETS_DIR "$HOME/.cache/dash_docsets"
|
||||||
direnv GNUPGHOME "$HOME/.config/gnupg"
|
direnv GNUPGHOME "$HOME/.config/gnupg"
|
||||||
direnv GOPATH "$HOME/.cache/go"
|
direnv GOPATH "$HOME/.cache/go"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
""" ALE """
|
""" ALE """
|
||||||
|
|
||||||
nmap <F3> :ALEFix<CR>
|
" nmap <F3> :ALEFix<CR>
|
||||||
|
"
|
||||||
let g:ale_sign_error = '×'
|
" let g:ale_sign_error = '×'
|
||||||
let g:ale_sign_warning = '!'
|
" let g:ale_sign_warning = '!'
|
||||||
let g:ale_completion_enabled = 1
|
" let g:ale_completion_enabled = 1
|
||||||
let g:ale_fixers = ['autopep8', 'shfmt', 'uncrustify', 'remove_trailing_lines', 'trim_whitespace', 'phpcbf']
|
" let g:ale_fixers = ['autopep8', 'shfmt', 'uncrustify', 'remove_trailing_lines', 'trim_whitespace', 'phpcbf']
|
||||||
let g:ale_php_phpcs_standard = '/srv/http/machines/ruleset.xml'
|
" let g:ale_php_phpcs_standard = '/srv/http/machines/ruleset.xml'
|
||||||
|
|
||||||
" For PHP, install https://pear.php.net/package/PHP_CodeSniffer
|
" For PHP, install https://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
|
||||||
|
@ -182,3 +182,6 @@ au FileType markdown vmap <Bar> :EasyAlign*<Bar><Enter>
|
||||||
" SmoothScroll
|
" SmoothScroll
|
||||||
noremap <silent> <C-K> :call smooth_scroll#up(20, 5, 1)<CR>
|
noremap <silent> <C-K> :call smooth_scroll#up(20, 5, 1)<CR>
|
||||||
noremap <silent> <C-J> :call smooth_scroll#down(20, 5, 1)<CR>
|
noremap <silent> <C-J> :call smooth_scroll#down(20, 5, 1)<CR>
|
||||||
|
|
||||||
|
" gutentags
|
||||||
|
let g:gutentags_cache_dir = expand('~/.cache/vim/tags')
|
||||||
|
|
|
@ -82,3 +82,6 @@ nmap <C-J> jjjjjjjjjjjjjjjjjjjjj
|
||||||
|
|
||||||
" \s to replace globally the word under the cursor
|
" \s to replace globally the word under the cursor
|
||||||
nnoremap <Leader>s :%s/\<<C-r><C-w>\>/
|
nnoremap <Leader>s :%s/\<<C-r><C-w>\>/
|
||||||
|
|
||||||
|
" add extensions to syntax
|
||||||
|
au BufNewFile,BufRead *.jinja set filetype=jinja2
|
||||||
|
|
Loading…
Reference in a new issue