From b54e0e5f399fc05c7f41cee3fd69218ec682c423 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 6 Oct 2023 21:20:32 +0100 Subject: [PATCH] [feature/ISSUE-25] Include itag and extension in filename --- source/app.d | 5 +++-- source/downloaders.d | 14 +++++++++----- source/parsers.d | 30 ++++++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/source/app.d b/source/app.d index c57b2a0..b36f697 100644 --- a/source/app.d +++ b/source/app.d @@ -86,7 +86,8 @@ void handleURL(string url, int itag, StdoutLogger logger, bool displayFormats, b logger.display(parser.getID()); logger.display(parser.getTitle()); - string filename = format!"%s-%s.mp4"(parser.getTitle(), parser.getID()).sanitizePath(); + YoutubeFormat youtubeFormat = parser.getFormat(itag); + string filename = format!"%s-%s-%d.%s"(parser.getTitle(), parser.getID(), itag, youtubeFormat.extension).sanitizePath(); logger.displayVerbose(filename); string destination = buildPath(getcwd(), filename); logger.displayVerbose(destination); @@ -112,7 +113,7 @@ void handleURL(string url, int itag, StdoutLogger logger, bool displayFormats, b if(parallel) { logger.display("Using ParallelDownloader"); - downloader = new ParallelDownloader(logger, parser.getID(), parser.getTitle()); + downloader = new ParallelDownloader(logger, parser.getID(), parser.getTitle(), youtubeFormat); } else { diff --git a/source/downloaders.d b/source/downloaders.d index 2b02712..1115ded 100644 --- a/source/downloaders.d +++ b/source/downloaders.d @@ -8,6 +8,8 @@ import std.range : iota; import std.net.curl : Curl, CurlOption; import helpers : getContentLength, sanitizePath, StdoutLogger, formatSuccess; +import parsers : YoutubeFormat; + interface Downloader { void download(string destination, string url, string referer); @@ -58,12 +60,14 @@ class ParallelDownloader : Downloader private StdoutLogger logger; private string id; private string title; + private YoutubeFormat youtubeFormat; - this(StdoutLogger logger, string id, string title) + this(StdoutLogger logger, string id, string title, YoutubeFormat youtubeFormat) { this.id = id; this.title = title; this.logger = logger; + this.youtubeFormat = youtubeFormat; } public void download(string destination, string url, string referer) @@ -82,8 +86,8 @@ class ParallelDownloader : Downloader { ulong[] offsets = calculateOffset(length, chunks, i); string partialLink = format!"%s&range=%d-%d"(url, offsets[0], offsets[1]); - string partialDestination = format!"%s-%s-%d-%d.mp4.part.%d"( - title, id, offsets[0], offsets[1], i + string partialDestination = format!"%s-%s-%d-%d-%d.%s.part.%d"( + title, id, youtubeFormat.itag, offsets[0], offsets[1], youtubeFormat.extension, i ).sanitizePath(); destinations[i] = partialDestination; @@ -133,7 +137,7 @@ class ParallelDownloader : Downloader unittest { - auto downloader = new ParallelDownloader(new StdoutLogger(), "", ""); + auto downloader = new ParallelDownloader(new StdoutLogger(), "", "", YoutubeFormat(18, 9371359, "360p", "video/mp4")); ulong length = 20 * 1024 * 1024; assert([0, 5 * 1024 * 1024] == downloader.calculateOffset(length, 4, 0)); assert([5 * 1024 * 1024 + 1, 10 * 1024 * 1024] == downloader.calculateOffset(length, 4, 1)); @@ -143,7 +147,7 @@ class ParallelDownloader : Downloader unittest { - auto downloader = new ParallelDownloader(new StdoutLogger(), "", ""); + auto downloader = new ParallelDownloader(new StdoutLogger(), "", "", YoutubeFormat(18, 9371359, "360p", "video/mp4")); ulong length = 23; assert([0, 5] == downloader.calculateOffset(length, 4, 0)); assert([5 + 1, 10] == downloader.calculateOffset(length, 4, 1)); diff --git a/source/parsers.d b/source/parsers.d index 4e4ca3b..c3ec8db 100644 --- a/source/parsers.d +++ b/source/parsers.d @@ -1,5 +1,4 @@ import std.json; -import std.algorithm : canFind; import std.net.curl : get; import std.uri : decodeComponent, encodeComponent; import std.stdio; @@ -8,7 +7,8 @@ import std.conv : to; import std.array : replace; import std.file : readText; import std.string : indexOf, format, lastIndexOf, split, strip; -import std.algorithm : reverse, map; +import std.algorithm : canFind, filter, reverse, map; +//import std.range : filter; import helpers : parseQueryString, matchOrFail, StdoutLogger; @@ -37,6 +37,17 @@ abstract class YoutubeVideoURLExtractor return meta.attr("content").idup; } + public YoutubeFormat getFormat(int itag) + { + YoutubeFormat[] formats = getFormats("formats") ~ getFormats("adaptiveFormats"); + auto match = formats.filter!(format => format.itag == itag); + if(match.empty) + { + throw new Exception("Unknown itag : " ~ itag.to!string); + } + return match.front(); + } + public YoutubeFormat[] getFormats() { return getFormats("formats") ~ getFormats("adaptiveFormats"); @@ -111,6 +122,17 @@ struct YoutubeFormat string quality; string mimetype; + string extension() @property nothrow + { + auto slashIndex = mimetype.indexOf("/"); + auto semicolonIndex = mimetype.indexOf(";"); + if(slashIndex == -1 || semicolonIndex == -1 || slashIndex >= semicolonIndex) + { + return "mp4"; + } + return mimetype[slashIndex + 1 .. semicolonIndex]; + } + string toString() { return format!`[%d] (%s) %s MB %s`( @@ -149,6 +171,10 @@ unittest assert(formats[15] == YoutubeFormat(249, 3314860, "tiny", `audio/webm; codecs="opus"`)); assert(formats[16] == YoutubeFormat(250, 4347447, "tiny", `audio/webm; codecs="opus"`)); assert(formats[17] == YoutubeFormat(251, 8650557, "tiny", `audio/webm; codecs="opus"`)); + + assert(YoutubeFormat(278, 6583212, "144p", `video/webm; codecs="vp9"`).extension == "webm"); + assert(YoutubeFormat(140, 9371359, "tiny", `audio/mp4; codecs="mp4a.40.2"`).extension == "mp4"); + assert(YoutubeFormat(140, 9371359, "unknown", `foobar`).extension == "mp4"); } class AdvancedYoutubeVideoURLExtractor : YoutubeVideoURLExtractor