Skip to content

Commit

Permalink
Add -chapter option to add video chapters rather than strip sponsors
Browse files Browse the repository at this point in the history
 - Add new argument parsing engine
 - Rewrite ydl in d to allow for better argument parsing
 - Add metadata writing
 - Update README.md
  • Loading branch information
faissaloo committed Sep 18, 2020
1 parent b76b53f commit ba24d34
Show file tree
Hide file tree
Showing 11 changed files with 482 additions and 147 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ sponskrub.dll
sponskrub.a
sponskrub.lib
sponskrub-test-*
/ydl
ydl.so
ydl.dylib
ydl.dll
ydl.a
ydl.lib
ydl-test-*
*.exe
*.o
*.obj
*.lst
*.mp4
*.ffm
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
This is a command line utility to strip out in video YouTube advertisements from downloaded YouTube videos, such as those downloaded from [youtube-dl](https://ytdl-org.github.io/youtube-dl/index.html). This means that you both don't waste disk space on adverts and don't redistribute adverts. If you want to download and strip a video you can use the wrapper application `ydl` which uses youtube-dl to download the video and will then strip the video of sponsors for you.

It makes use of the [SponsorBlock API](https://github.com/ajayyy/SponsorBlockServer#api-docs) and I'd recommend installing the extension and maybe contributing some sponsorship times when you're ever bored.
You can build it by running `dub build`.
You can build `ydl` and `sponskrub` by running `dub build`, to build only one of them use `dub build :ydl` and `dub build :sponskrub` respectively.

![before and after SponSkrub](repo_images/before_after.png)
28 changes: 27 additions & 1 deletion dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,31 @@
"copyright": "Copyright © 2019, Faissal Isslam Bensefia",
"description": "automatically cut out a downloaded YouTube video's sponsors",
"license": "GPLv3",
"name": "sponskrub"
"name": "sponskrub",
"configurations": [
{
"name": "all",
"targetType": "none",
"dependencies": {
"sponskrub:sponskrub": "*",
"sponskrub:ydl": "*"
}
}
],
"subPackages": [
{
"name": "sponskrub",
"targetType": "executable",
"mainSourceFile": "src/sponskrub/sponskrub.d",
"excludedSourceFiles": [ "src/ydl/*"],
"targetName": "./sponskrub"
},
{
"name": "ydl",
"targetType": "executable",
"mainSourceFile": "src/ydl/ydl.d",
"excludedSourceFiles": [ "src/sponskrub/*"],
"targetName": "./ydl"
}
]
}
5 changes: 5 additions & 0 deletions dub.selections.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"fileVersion": 1,
"versions": {
}
}
118 changes: 0 additions & 118 deletions source/app.d

This file was deleted.

125 changes: 125 additions & 0 deletions src/common/args.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
This file is part of SponSkrub.
SponSkrub is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SponSkrub is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SponSkrub. If not, see <https://www.gnu.org/licenses/>.
*/
module args;
import std.container.array;
import std.stdio;
import std.typecons;
import std.algorithm;
import std.array;

class ArgTemplate {
string name;
bool required;
bool flag;
int subarguments;

this(string name) {
this.name = name;
this.required = true;
this.flag = false;
this.subarguments = false;
}

this(string name, bool flag) {
this.name = name;
this.required = false;
this.flag = flag;
this.subarguments = false;
}

this(string name, bool flag, bool required, int subarguments) {
this.name = name;
this.flag = flag;
this.required = required;
this.subarguments = subarguments;
}
}

class Args {
ArgTemplate[] required_positional_argument_templates;
ArgTemplate[string] required_flag_argument_templates;

ArgTemplate[] positional_argument_templates;
ArgTemplate[string] flag_argument_templates;

string[] positional_arguments;
string[][string] flag_arguments;

string[] unrecognised_arguments;

this(ArgTemplate[] args_templates) {
foreach (ArgTemplate arg_template; args_templates) {
if (arg_template.flag) {
flag_argument_templates[arg_template.name] = arg_template;
if (arg_template.required) {
required_flag_argument_templates[arg_template.name] = arg_template;
}
} else {
positional_argument_templates ~= arg_template;
if (arg_template.required) {
required_positional_argument_templates ~= arg_template;
}
}
}
}

void parse(string[] args) {
int remaining_subarguments = 0;
string flag_name;

foreach (string arg; args) {
if (remaining_subarguments > 0) {
flag_arguments[flag_name] ~= arg;
remaining_subarguments--;
} else {
if (arg[0] == '-') {
flag_name = arg[1..$];
if (flag_name in flag_argument_templates) {
flag_arguments[flag_name] = [];
if (flag_argument_templates[flag_name].subarguments > 0) {
remaining_subarguments = flag_argument_templates[flag_name].subarguments;
}
} else {
unrecognised_arguments ~= arg;
}
} else {
if (positional_arguments.length >= positional_argument_templates.length) {
unrecognised_arguments ~= arg;
} else {
positional_arguments ~= arg;
}
}
}
}
}

string[] get_missing_arguments() {
string[] missing_arguments;

foreach (string required_flag; required_flag_argument_templates.byKey()) {
if (required_flag !in flag_arguments) {
missing_arguments ~= required_flag;
}
}

if (positional_arguments.length < required_positional_argument_templates.length) {
missing_arguments ~= required_positional_argument_templates[positional_arguments.length..$].map!(x => x.name).array;
}

return missing_arguments;
}
}
29 changes: 29 additions & 0 deletions source/ffwrap.d → src/sponskrub/ffwrap.d
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
module ffwrap;
import std.process;
import std.string;
import std.mmfile;
import std.file;
import std.range;
import std.random;
import std.algorithm;

string get_video_duration(string filename) {
auto ffprobe_process = execute(["ffprobe", "-v", "quiet", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", filename]);
Expand All @@ -32,3 +37,27 @@ bool run_ffmpeg_filter(string input_filename, string output_filename, string fil
auto ffmpeg_process = spawnProcess(["ffmpeg", "-i", input_filename, "-filter_complex", filter, "-map", "[v]", "-map", "[a]",output_filename]);
return wait(ffmpeg_process) == 0;
}

bool add_ffmpeg_metadata(string input_filename, string output_filename, string metadata) {
string metadata_filename = prepend_random_prefix(6, "-metadata.ffm");
scope(exit) {
remove(metadata_filename);
}

write_metadata(metadata_filename, metadata);
auto ffmpeg_process = spawnProcess(["ffmpeg", "-i", input_filename, "-i", metadata_filename, "-map_metadata", "1", "-map_chapters", "1", "-vcodec", "copy", output_filename]);
auto result = wait(ffmpeg_process) == 0;

return result;
}

auto write_metadata(string filename, string metadata) {
auto file = new MmFile(filename, MmFile.Mode.readWriteNew, metadata.length+2, null);
ubyte[] data = cast(ubyte[]) file[0..metadata.length];
data[] = cast(ubyte[]) metadata[];
}

string prepend_random_prefix(int length, string suffix) {
auto prefix = iota(length).map!((_) => "abcdefghijklmnopqrstuvwxyz0123456789"[uniform(0,$)]).array;
return prefix ~ suffix;
}
Loading

0 comments on commit ba24d34

Please sign in to comment.