From 94a29fd63ff6bb0c1805c44ef5ebf4d915427454 Mon Sep 17 00:00:00 2001 From: talanc Date: Sun, 8 Aug 2021 21:20:19 +1000 Subject: [PATCH] [YouTube] csv and zip subscription import (Google Takeout) csv: Improved error messages Exits early if it hasnt found any items in the first few lines zip: Now checks all CSV files instead of hard-coded paths final qualifiers for immutable locals and parameters Co-authored-by: litetex <40789489+litetex@users.noreply.github.com> --- .../YoutubeSubscriptionExtractor.java | 136 +++++++++++++- .../subscription/SubscriptionExtractor.java | 11 ++ .../YoutubeSubscriptionExtractorTest.java | 45 +++++ .../youtube_takeout_import_test_1.csv | 7 + .../youtube_takeout_import_test_1.zip | Bin 0 -> 915 bytes .../youtube_takeout_import_test_2.csv | 174 ++++++++++++++++++ .../youtube_takeout_import_test_2.zip | Bin 0 -> 5690 bytes 7 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 extractor/src/test/resources/youtube_takeout_import_test_1.csv create mode 100644 extractor/src/test/resources/youtube_takeout_import_test_1.zip create mode 100644 extractor/src/test/resources/youtube_takeout_import_test_2.csv create mode 100644 extractor/src/test/resources/youtube_takeout_import_test_2.zip diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeSubscriptionExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeSubscriptionExtractor.java index be088532..aafb1cd1 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeSubscriptionExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeSubscriptionExtractor.java @@ -10,17 +10,22 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeService; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.extractor.subscription.SubscriptionItem; +import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import javax.annotation.Nonnull; import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.INPUT_STREAM; /** - * Extract subscriptions from a Google takeout export (the user has to get the JSON out of the zip) + * Extract subscriptions from a Google takeout export */ public class YoutubeSubscriptionExtractor extends SubscriptionExtractor { private static final String BASE_CHANNEL_URL = "https://www.youtube.com/channel/"; @@ -37,6 +42,30 @@ public class YoutubeSubscriptionExtractor extends SubscriptionExtractor { @Override public List fromInputStream(@Nonnull final InputStream contentInputStream) throws ExtractionException { + return fromJsonInputStream(contentInputStream); + } + + @Override + public List fromInputStream(@Nonnull final InputStream contentInputStream, String contentType) + throws ExtractionException { + switch (contentType) { + case "json": + case "application/json": + return fromJsonInputStream(contentInputStream); + case "csv": + case "text/csv": + case "text/comma-separated-values": + return fromCsvInputStream(contentInputStream); + case "zip": + case "application/zip": + return fromZipInputStream(contentInputStream); + default: + throw new InvalidSourceException("Unsupported content type: " + contentType); + } + } + + public List fromJsonInputStream(@Nonnull final InputStream contentInputStream) + throws ExtractionException { final JsonArray subscriptions; try { subscriptions = JsonParser.array().from(contentInputStream); @@ -68,4 +97,109 @@ public class YoutubeSubscriptionExtractor extends SubscriptionExtractor { } return subscriptionItems; } + + public List fromZipInputStream(@Nonnull final InputStream contentInputStream) + throws ExtractionException { + final ZipInputStream zipInputStream = new ZipInputStream(contentInputStream); + + try { + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + if (zipEntry.getName().toLowerCase().endsWith(".csv")) { + try { + final List csvItems = fromCsvInputStream(zipInputStream); + + // Return it only if it has items (it exits early if it's the wrong file format) + // Otherwise try the next file + if (csvItems.size() > 0) { + return csvItems; + } + } catch (ExtractionException e) { + // Ignore error and go to next file + // (maybe log it?) + } + } + } + } catch (IOException e) { + throw new InvalidSourceException("Error reading contents of zip file", e); + } + + throw new InvalidSourceException("Unable to find a valid subscriptions.csv file (try extracting and selecting the csv file)"); + } + + public List fromCsvInputStream(@Nonnull final InputStream contentInputStream) + throws ExtractionException { + // Expected format of CSV file: + // Channel Id,Channel Url,Channel Title + // UC1JTQBa5QxZCpXrFSkMxmPw,http://www.youtube.com/channel/UC1JTQBa5QxZCpXrFSkMxmPw,Raycevick + // UCFl7yKfcRcFmIUbKeCA-SJQ,http://www.youtube.com/channel/UCFl7yKfcRcFmIUbKeCA-SJQ,Joji + // + // Notes: + // It's always 3 columns + // The first line is always a header + // Header names are different based on the locale + // Fortunately the data is always the same order no matter what locale + + int currentLine = 0; + String line = ""; + + try (BufferedReader br = new BufferedReader(new InputStreamReader(contentInputStream))) { + final List subscriptionItems = new ArrayList<>(); + + // Ignore header + currentLine = 1; + line = br.readLine(); + + while ((line = br.readLine()) != null) { + currentLine++; + + // Exit early if we've read the first few lines and we haven't added any items + // It's likely we're in the wrong file + if (currentLine > 5 && subscriptionItems.size() == 0) { + break; + } + + // First comma + int i1 = line.indexOf(","); + if (i1 == -1) { + continue; + } + + // Second comma + int i2 = line.indexOf(",", i1 + 1); + if (i2 == -1) { + continue; + } + + // Third comma or line length + int i3 = line.indexOf(",", i2 + 1); + if (i3 == -1) { + i3 = line.length(); + } + + // Channel URL from second entry + final String channelUrl = line + .substring(i1 + 1, i2) + .replace("http://", "https://"); + if (!channelUrl.startsWith(BASE_CHANNEL_URL)) { + continue; + } + + // Channel title from third entry + final String channelTitle = line.substring(i2 + 1, i3); + + final SubscriptionItem newItem = new SubscriptionItem(service.getServiceId(), channelUrl, channelTitle); + subscriptionItems.add(newItem); + } + + return subscriptionItems; + } catch (IOException e) { + if (line == null) { + line = ""; + } else if (line.length() > 10) { + line = line.substring(0, 10) + "..."; + } + throw new InvalidSourceException("Error reading CSV file, line = '" + line + "', line number = " + currentLine); + } + } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/subscription/SubscriptionExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/subscription/SubscriptionExtractor.java index 8a31b0f7..b2a2d75e 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/subscription/SubscriptionExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/subscription/SubscriptionExtractor.java @@ -77,4 +77,15 @@ public abstract class SubscriptionExtractor { throw new UnsupportedOperationException("Service " + service.getServiceInfo().getName() + " doesn't support extracting from an InputStream"); } + + /** + * Reads and parse a list of {@link SubscriptionItem} from the given InputStream. + * + * @throws InvalidSourceException when the content read from the InputStream is invalid and can not be parsed + */ + public List fromInputStream(@Nonnull final InputStream contentInputStream, String contentType) + throws ExtractionException { + throw new UnsupportedOperationException("Service " + service.getServiceInfo().getName() + + " doesn't support extracting from an InputStream"); + } } diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSubscriptionExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSubscriptionExtractorTest.java index c8f87a90..e00601ac 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSubscriptionExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSubscriptionExtractorTest.java @@ -11,6 +11,7 @@ import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.extractor.subscription.SubscriptionItem; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.FileInputStream; import java.util.Arrays; import java.util.List; @@ -110,4 +111,48 @@ public class YoutubeSubscriptionExtractorTest { } } } + + private static void assertSubscriptionItems(final List subscriptionItems) + throws Exception { + assertTrue(subscriptionItems.size() > 0); + + for (final SubscriptionItem item : subscriptionItems) { + assertNotNull(item.getName()); + assertNotNull(item.getUrl()); + assertTrue(urlHandler.acceptUrl(item.getUrl())); + assertEquals(ServiceList.YouTube.getServiceId(), item.getServiceId()); + } + } + + @Test + public void fromZipInputStream() throws Exception { + final List zipPaths = Arrays.asList( + "youtube_takeout_import_test_1.zip", + "youtube_takeout_import_test_2.zip" + ); + + for (final String path : zipPaths) + { + final File file = resolveTestResource(path); + final FileInputStream fileInputStream = new FileInputStream(file); + final List subscriptionItems = subscriptionExtractor.fromZipInputStream(fileInputStream); + assertSubscriptionItems(subscriptionItems); + } + } + + @Test + public void fromCsvInputStream() throws Exception { + final List csvPaths = Arrays.asList( + "youtube_takeout_import_test_1.csv", + "youtube_takeout_import_test_2.csv" + ); + + for (String path : csvPaths) + { + final File file = resolveTestResource(path); + final FileInputStream fileInputStream = new FileInputStream(file); + final List subscriptionItems = subscriptionExtractor.fromCsvInputStream(fileInputStream); + assertSubscriptionItems(subscriptionItems); + } + } } diff --git a/extractor/src/test/resources/youtube_takeout_import_test_1.csv b/extractor/src/test/resources/youtube_takeout_import_test_1.csv new file mode 100644 index 00000000..b71d9af2 --- /dev/null +++ b/extractor/src/test/resources/youtube_takeout_import_test_1.csv @@ -0,0 +1,7 @@ +Channel Id,Channel Url,Channel Title +UC1JTQBa5QxZCpXrFSkMxmPw,http://www.youtube.com/channel/UC1JTQBa5QxZCpXrFSkMxmPw,Raycevick +UC3ltptWa0xfrDweghW94Acg,http://www.youtube.com/channel/UC3ltptWa0xfrDweghW94Acg,Karl Jobst +UC9PBzalIcEQCsiIkq36PyUA,http://www.youtube.com/channel/UC9PBzalIcEQCsiIkq36PyUA,Digital Foundry +UCU64AfivgQUOPuIJ8N5YaCA,http://www.youtube.com/channel/UCU64AfivgQUOPuIJ8N5YaCA,EventStatus +UCsvn_Po0SmunchJYOWpOxMg,http://www.youtube.com/channel/UCsvn_Po0SmunchJYOWpOxMg,videogamedunkey + diff --git a/extractor/src/test/resources/youtube_takeout_import_test_1.zip b/extractor/src/test/resources/youtube_takeout_import_test_1.zip new file mode 100644 index 0000000000000000000000000000000000000000..69a0e7b3c45247b7b31da0d33d4528c54296d0e1 GIT binary patch literal 915 zcmWIWW@Zs#00GG*9Km1)l#l_^A&J?k`K2ZLiABj7nPsW*Nk#eP#i>Pl86~+n0dSqG z7NhD^Led$TUm8-Hl&X-Jm!be;`<51GChOzYZ$Om(;?ktz3@?GM z*~e8N@g8W(A4UcSM>0%9F1)j#b+*T3SfT79AB*K6@T>yg=2o%61p;Uq}30pdW7$E?ptbiDn-e4&OTdKir z9w^;F0LZ*5ASP@cN=zg98+$qena&9I_X#pgN3jc(ssg-O*+53J0AV^XsfsaycmS(H B7wP~2 literal 0 HcmV?d00001 diff --git a/extractor/src/test/resources/youtube_takeout_import_test_2.csv b/extractor/src/test/resources/youtube_takeout_import_test_2.csv new file mode 100644 index 00000000..a718ee82 --- /dev/null +++ b/extractor/src/test/resources/youtube_takeout_import_test_2.csv @@ -0,0 +1,174 @@ +ID del canal,URL del canal,Título del canal +UC-UcKEWOSpz0AfY33TrYw3g,http://www.youtube.com/channel/UC-UcKEWOSpz0AfY33TrYw3g,Nekojitablog +UC-lHJZR3Gqxm24_Vd_AJ5Yw,http://www.youtube.com/channel/UC-lHJZR3Gqxm24_Vd_AJ5Yw,PewDiePie +UC-uZVnE4p_52zXbH34S4pcA,http://www.youtube.com/channel/UC-uZVnE4p_52zXbH34S4pcA,CRD Сreative +UC08if0oBaJcpxQQaa7wJZ0g,http://www.youtube.com/channel/UC08if0oBaJcpxQQaa7wJZ0g,Gerry Studios +UC0B7gD411SJ4DkQYnSwFAcQ,http://www.youtube.com/channel/UC0B7gD411SJ4DkQYnSwFAcQ,potasticp +UC0KfjyvabuE2J-RBC6ko2Lw,http://www.youtube.com/channel/UC0KfjyvabuE2J-RBC6ko2Lw,Mac Address +UC0pHpxSt_4gd63WylQL0cVQ,http://www.youtube.com/channel/UC0pHpxSt_4gd63WylQL0cVQ,Pipepino +UC1_aBBbg4vzI_Y8QAolPcCA,http://www.youtube.com/channel/UC1_aBBbg4vzI_Y8QAolPcCA,KillerCreeper55 - Minecraft +UC1n_PfsVqxllCcnMPlxBIjw,http://www.youtube.com/channel/UC1n_PfsVqxllCcnMPlxBIjw,Wilbur Soot +UC21XRv2sCF43MKL1WKHAP9w,http://www.youtube.com/channel/UC21XRv2sCF43MKL1WKHAP9w,We Skeem +UC29GGV1kYu1MlpS6VRhIRZw,http://www.youtube.com/channel/UC29GGV1kYu1MlpS6VRhIRZw,ZellenDust +UC29RfHuFFanQjXSX7XaFHuw,http://www.youtube.com/channel/UC29RfHuFFanQjXSX7XaFHuw,Julieta Appoloni +UC2sOV9xZJ8m21bi7WUmTBIw,http://www.youtube.com/channel/UC2sOV9xZJ8m21bi7WUmTBIw,Fzst +UC34DnjIkx6EJLk6pwlDoGTQ,http://www.youtube.com/channel/UC34DnjIkx6EJLk6pwlDoGTQ,EsVandal +UC36xmz34q02JYaZYKrMwXng,http://www.youtube.com/channel/UC36xmz34q02JYaZYKrMwXng,Nate Gentile +UC3jSNmKWYA04R47fDcc1ImA,http://www.youtube.com/channel/UC3jSNmKWYA04R47fDcc1ImA,InfinitelyGalactic +UC3zd273Sh1s2HPdGAmITZxQ,http://www.youtube.com/channel/UC3zd273Sh1s2HPdGAmITZxQ,IbraH +UC4jKcxlBvTZpkPyatMMfgpQ,http://www.youtube.com/channel/UC4jKcxlBvTZpkPyatMMfgpQ,Anna Aerien +UC5SCKE-sVHhdS57hNhzkKTA,http://www.youtube.com/channel/UC5SCKE-sVHhdS57hNhzkKTA,COSMIC KID +UC5UAwBUum7CPN5buc-_N1Fw,http://www.youtube.com/channel/UC5UAwBUum7CPN5buc-_N1Fw,The Linux Experiment +UC6UjsfZ7PNcCF51JAgh5EIQ,http://www.youtube.com/channel/UC6UjsfZ7PNcCF51JAgh5EIQ,Tkeio ! +UC71kKpUN9OQMl_HivC19A_g,http://www.youtube.com/channel/UC71kKpUN9OQMl_HivC19A_g,Luh +UC7AHXsGbhbGwFhk8FcLqNIQ,http://www.youtube.com/channel/UC7AHXsGbhbGwFhk8FcLqNIQ,Jake Marley +UC7Jwj9fkrf1adN4fMmTkpug,http://www.youtube.com/channel/UC7Jwj9fkrf1adN4fMmTkpug,DankPods +UC7YOGHUfC1Tb6E4pudI9STA,http://www.youtube.com/channel/UC7YOGHUfC1Tb6E4pudI9STA,Mental Outlaw +UC7qdNWFCD3lo8toe0TsaeUw,http://www.youtube.com/channel/UC7qdNWFCD3lo8toe0TsaeUw,CrisGreen ( ͡° 3 ͡°) +UC8JUloCS-XOMOi9w_JLegDw,http://www.youtube.com/channel/UC8JUloCS-XOMOi9w_JLegDw,yuganlovetacos +UC8qKhubxKNyX5A_cXz7e_vg,http://www.youtube.com/channel/UC8qKhubxKNyX5A_cXz7e_vg,Ouros +UC9_krwU8wTPCZRKaf34QnPw,http://www.youtube.com/channel/UC9_krwU8wTPCZRKaf34QnPw,Farfadox +UC9n8N8u98RVbhOTTjXpyR5A,http://www.youtube.com/channel/UC9n8N8u98RVbhOTTjXpyR5A,La Piña Kawaii +UCAZlPagsIabYqmZEz80Ov2Q,http://www.youtube.com/channel/UCAZlPagsIabYqmZEz80Ov2Q,Metrica +UCAmralsz60sfzuxucXHIuXA,http://www.youtube.com/channel/UCAmralsz60sfzuxucXHIuXA,Devil Haru Ch. +UCB18IliCiBeraa8oysaTZSQ,http://www.youtube.com/channel/UCB18IliCiBeraa8oysaTZSQ,Miraie +UCBa659QWEk1AI4Tg--mrJ2A,http://www.youtube.com/channel/UCBa659QWEk1AI4Tg--mrJ2A,Tom Scott +UCC4s-auWsFG53zpcqCMyD1Q,http://www.youtube.com/channel/UCC4s-auWsFG53zpcqCMyD1Q,yaco beltran +UCC8u8qnUYqXpjQkdqTcfoAg,http://www.youtube.com/channel/UCC8u8qnUYqXpjQkdqTcfoAg,SupraClips +UCDK9qD5DAQML-pzrtA7A4oA,http://www.youtube.com/channel/UCDK9qD5DAQML-pzrtA7A4oA,OfflineTV +UCDPv4No6VB2L5tz-ucdE66g,http://www.youtube.com/channel/UCDPv4No6VB2L5tz-ucdE66g,Denis Orlando +UCE5ZRpNnq2kJI0mza6a2w6g,http://www.youtube.com/channel/UCE5ZRpNnq2kJI0mza6a2w6g,SupraPixel X +UCEG3ulBltfrvOE8baBogJ0g,http://www.youtube.com/channel/UCEG3ulBltfrvOE8baBogJ0g,ZONE TOONS +UCEuANnOyczsSWmabtxg1kIA,http://www.youtube.com/channel/UCEuANnOyczsSWmabtxg1kIA,ivo31 YT +UCFR2oaNj02WnXkOgLH0iqOA,http://www.youtube.com/channel/UCFR2oaNj02WnXkOgLH0iqOA,Auron +UCFl7yKfcRcFmIUbKeCA-SJQ,http://www.youtube.com/channel/UCFl7yKfcRcFmIUbKeCA-SJQ,Joji +UCFz9NxZISfloIeqaTUovmrQ,http://www.youtube.com/channel/UCFz9NxZISfloIeqaTUovmrQ,Pedo Gaming +UCGntRpUdlsUG0tcvj2zIAIQ,http://www.youtube.com/channel/UCGntRpUdlsUG0tcvj2zIAIQ,FranzJ +UCGsVaz3QTFdspLqt79jIuPA,http://www.youtube.com/channel/UCGsVaz3QTFdspLqt79jIuPA,Guinxu +UCGwu0nbY2wSkW8N-cghnLpA,http://www.youtube.com/channel/UCGwu0nbY2wSkW8N-cghnLpA,Jaiden Animations +UCHMo5_1W1bRHapGOTM5ieIg,http://www.youtube.com/channel/UCHMo5_1W1bRHapGOTM5ieIg,Conterstine +UCHXJ0dhS3NpTBFg7lR_5w8Q,http://www.youtube.com/channel/UCHXJ0dhS3NpTBFg7lR_5w8Q,Also Fitz +UCHZ986wm_sJT6wntdDTIIcw,http://www.youtube.com/channel/UCHZ986wm_sJT6wntdDTIIcw,FitMC +UCI4i5O3BPZZWbbX4J345crA,http://www.youtube.com/channel/UCI4i5O3BPZZWbbX4J345crA,Mery Soldier +UCIbTKu0fPqqCOIW-BNeOqdA,http://www.youtube.com/channel/UCIbTKu0fPqqCOIW-BNeOqdA,Shadoune666 +UCJdoOeD2N8sM_LEREHG6kYQ,http://www.youtube.com/channel/UCJdoOeD2N8sM_LEREHG6kYQ,omotea +UCJxv80y78XTCUqfiplmDyRw,http://www.youtube.com/channel/UCJxv80y78XTCUqfiplmDyRw,Tri-line +UCK1W7jhV8g0JuAJbaZYyehA,http://www.youtube.com/channel/UCK1W7jhV8g0JuAJbaZYyehA,Dylantero Sin Imaginación +UCKHR-onQ7E0vyrEWgxiRljQ,http://www.youtube.com/channel/UCKHR-onQ7E0vyrEWgxiRljQ,kingani +UCM66PelO5iYSanEGWowaCVA,http://www.youtube.com/channel/UCM66PelO5iYSanEGWowaCVA,Gissely +UCNJL4PCMVJVNcYWPSH009rg,http://www.youtube.com/channel/UCNJL4PCMVJVNcYWPSH009rg,NightcoreChase +UCNYW2vfGrUE6R5mIJYzkRyQ,http://www.youtube.com/channel/UCNYW2vfGrUE6R5mIJYzkRyQ,DrossRotzank +UCOGLfpN-nkRjPZz2w1dTuew,http://www.youtube.com/channel/UCOGLfpN-nkRjPZz2w1dTuew,TGet! +UCP61sZSPUlFsDdCm72Z6b-Q,http://www.youtube.com/channel/UCP61sZSPUlFsDdCm72Z6b-Q,Atrial +UCPbGiUt4Yu8EsaFM-KAmgjg,http://www.youtube.com/channel/UCPbGiUt4Yu8EsaFM-KAmgjg,Grizzy +UCPr3T20IPZ03gU9gZ2BwM5g,http://www.youtube.com/channel/UCPr3T20IPZ03gU9gZ2BwM5g,The Click +UCQ-JZLA6Zbwffkl4VGdeCYw,http://www.youtube.com/channel/UCQ-JZLA6Zbwffkl4VGdeCYw,Matias Candia +UCQCnMFr8uHBaMTaNBhych_A,http://www.youtube.com/channel/UCQCnMFr8uHBaMTaNBhych_A,Mini Ladd +UCQDhxkSxZA6lxdeXE19aoRA,http://www.youtube.com/channel/UCQDhxkSxZA6lxdeXE19aoRA,Hugh Jeffreys +UCS-WzPVpAAli-1IfEG2lN8A,http://www.youtube.com/channel/UCS-WzPVpAAli-1IfEG2lN8A,Michael MJD +UCS3Zvg_emGJF6RNgWyXOynw,http://www.youtube.com/channel/UCS3Zvg_emGJF6RNgWyXOynw,Jake Frew +UCSa0hnM2n_yvOnecoQ2fgxA,http://www.youtube.com/channel/UCSa0hnM2n_yvOnecoQ2fgxA,Jamie Pine +UCScy7OHZSdRTCbs_KPsutcA,http://www.youtube.com/channel/UCScy7OHZSdRTCbs_KPsutcA,Landidzu +UCStaCkxg1uRCjYWsgQFu8zQ,http://www.youtube.com/channel/UCStaCkxg1uRCjYWsgQFu8zQ,MxR Doesn't Play +UCSunSIhBSrgzYzx2Yd5Rxaw,http://www.youtube.com/channel/UCSunSIhBSrgzYzx2Yd5Rxaw,Frankkaster +UCT35hFvV0j8SR3SBGJChKZQ,http://www.youtube.com/channel/UCT35hFvV0j8SR3SBGJChKZQ,Jota. +UCTA1snhmGZapRRilB6VzJGA,http://www.youtube.com/channel/UCTA1snhmGZapRRilB6VzJGA,Silithur V +UCVBkwO6Ok1De0UfNZdo7-Ag,http://www.youtube.com/channel/UCVBkwO6Ok1De0UfNZdo7-Ag,DayoScript +UCVRWkwOQIpxPOhQ9V-gL2UA,http://www.youtube.com/channel/UCVRWkwOQIpxPOhQ9V-gL2UA,SIAMES +UCVUVc506EM6TG-7wmv3i2gA,http://www.youtube.com/channel/UCVUVc506EM6TG-7wmv3i2gA,¡ KHAZOO ! +UCVls1GmFKf6WlTraIb_IaJg,http://www.youtube.com/channel/UCVls1GmFKf6WlTraIb_IaJg,DistroTube +UCVrrU-Z0cxcVIX9uq40LoqA,http://www.youtube.com/channel/UCVrrU-Z0cxcVIX9uq40LoqA,Gona89 +UCWNxXrELNgyeJgYcF3wEJ-A,http://www.youtube.com/channel/UCWNxXrELNgyeJgYcF3wEJ-A,Indiruskis Official +UCWYmWfb9O-Di7cOVUzBpRHA,http://www.youtube.com/channel/UCWYmWfb9O-Di7cOVUzBpRHA,Rubik +UCX3qiJ-Ivoke80Tklb49dDg,http://www.youtube.com/channel/UCX3qiJ-Ivoke80Tklb49dDg,Shadow! +UCX6OQ3DkcsbYNE6H8uQQuVA,http://www.youtube.com/channel/UCX6OQ3DkcsbYNE6H8uQQuVA,MrBeast +UCXazgXDIYyWH-yXLAkcrFxw,http://www.youtube.com/channel/UCXazgXDIYyWH-yXLAkcrFxw,elrubiusOMG +UCXdWux01giurAUf2fPjXyRQ,http://www.youtube.com/channel/UCXdWux01giurAUf2fPjXyRQ,ZellenDos - ZellenDust en version extendida (GAMEPLAYS) +UCXogpAmELid1pTAx5KxJzGw,http://www.youtube.com/channel/UCXogpAmELid1pTAx5KxJzGw,Sendates +UCXuqSBlHAE6Xw-yeJA0Tunw,http://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw,Linus Tech Tips +UCY2ekMrWhsUVHwO3J3-PCzQ,http://www.youtube.com/channel/UCY2ekMrWhsUVHwO3J3-PCzQ,steveee +UCYkKGJj-G_W2vipGnuJOrKA,http://www.youtube.com/channel/UCYkKGJj-G_W2vipGnuJOrKA,Pato Asado +UCZU_0ozD_jE_3X0txiK92rA,http://www.youtube.com/channel/UCZU_0ozD_jE_3X0txiK92rA,Rusbi +UCZX4xIxNNwf84kN9Mi4lu2A,http://www.youtube.com/channel/UCZX4xIxNNwf84kN9Mi4lu2A,HakuEXE +UCZpLsmMwIECgY5j6IsOqNwQ,http://www.youtube.com/channel/UCZpLsmMwIECgY5j6IsOqNwQ,orslok +UC_63umCUF3kuSwnhpUyhemg,http://www.youtube.com/channel/UC_63umCUF3kuSwnhpUyhemg,MxR Shorts +UC_jx6UXvRPu5g_BsSLwlMGw,http://www.youtube.com/channel/UC_jx6UXvRPu5g_BsSLwlMGw,Los Vlogs de Dross +UC_qspxIbv_iMtfg7rJfbyOg,http://www.youtube.com/channel/UC_qspxIbv_iMtfg7rJfbyOg,Japatonic LITE +UCa3DVlGH2_QhvwuWlPa6MDQ,http://www.youtube.com/channel/UCa3DVlGH2_QhvwuWlPa6MDQ,Jaime Altozano +UCa56-DbKF-j9rlgKNKt6z6w,http://www.youtube.com/channel/UCa56-DbKF-j9rlgKNKt6z6w,G1G0 +UCaHT88aobpcvRFEuy4v5Clg,http://www.youtube.com/channel/UCaHT88aobpcvRFEuy4v5Clg,Lessons in Meme Culture +UCamLstJyCa-t5gfZegxsFMw,http://www.youtube.com/channel/UCamLstJyCa-t5gfZegxsFMw,Colin and Samir +UCbvbFVlbArZWdouAqWKOVJg,http://www.youtube.com/channel/UCbvbFVlbArZWdouAqWKOVJg,Nexpo: After Hours +UCcjIvuxmWlS5IEQ0JdPV4Ng,http://www.youtube.com/channel/UCcjIvuxmWlS5IEQ0JdPV4Ng,Rubius Z +UCd3aWvyGFbNjsJV0S7G-tgw,http://www.youtube.com/channel/UCd3aWvyGFbNjsJV0S7G-tgw,Avdan +UCd4XwUn2Lure2NHHjukoCwA,http://www.youtube.com/channel/UCd4XwUn2Lure2NHHjukoCwA,Linux For Everyone +UCdBK94H6oZT2Q7l0-b0xmMg,http://www.youtube.com/channel/UCdBK94H6oZT2Q7l0-b0xmMg,ShortCircuit +UCdRx6BjmUwdP7pfGIYPRHaQ,http://www.youtube.com/channel/UCdRx6BjmUwdP7pfGIYPRHaQ,alexelcapo +UCeeFfhMcJa1kjtfZAGskOCA,http://www.youtube.com/channel/UCeeFfhMcJa1kjtfZAGskOCA,TechLinked +UCfgV8Gpt4ZkbvJFBpxpi7JA,http://www.youtube.com/channel/UCfgV8Gpt4ZkbvJFBpxpi7JA,Keisari +UCg3qsVzHeUt5_cPpcRtoaJQ,http://www.youtube.com/channel/UCg3qsVzHeUt5_cPpcRtoaJQ,圧倒的不審者の極み! +UCg6gPGh8HU2U01vaFCAsvmQ,http://www.youtube.com/channel/UCg6gPGh8HU2U01vaFCAsvmQ,Chris Titus Tech +UCgBS3WiEFJxUj4N-_xQcpPA,http://www.youtube.com/channel/UCgBS3WiEFJxUj4N-_xQcpPA,DEFI BRILATOR +UChBSlTzHKUabiXaIXd3y0rg,http://www.youtube.com/channel/UChBSlTzHKUabiXaIXd3y0rg,Furro Azul +UChFm1c5n1zlqK4-EitRPPRQ,http://www.youtube.com/channel/UChFm1c5n1zlqK4-EitRPPRQ,La red de Mario +UChRV-sCl5Fma4qm_hFpDKBQ,http://www.youtube.com/channel/UChRV-sCl5Fma4qm_hFpDKBQ,Ephemeral Rift +UCjJ_2P7-Xx9Fb7zHea9mqSw,http://www.youtube.com/channel/UCjJ_2P7-Xx9Fb7zHea9mqSw,Derpixon +UCjSEJkpGbcZhvo0lr-44X_w,http://www.youtube.com/channel/UCjSEJkpGbcZhvo0lr-44X_w,TechHut +UCjxeK73CBe_ftHp16xnABDA,http://www.youtube.com/channel/UCjxeK73CBe_ftHp16xnABDA,Dualitty +UCkPqufdW60E1z9vHfigA8QQ,http://www.youtube.com/channel/UCkPqufdW60E1z9vHfigA8QQ,Nico Paviolo +UCkPsz9QkX5rKlKiPV3ymltA,http://www.youtube.com/channel/UCkPsz9QkX5rKlKiPV3ymltA,NarkNation +UCkitABalXafr-NqceQdDXtg,http://www.youtube.com/channel/UCkitABalXafr-NqceQdDXtg,TVFilthyFrank +UCkqcbakfUZ_tRF_12AkSK9A,http://www.youtube.com/channel/UCkqcbakfUZ_tRF_12AkSK9A,Giuly Cobian +UCku1mRyxRdPxy_WuWcuGZow,http://www.youtube.com/channel/UCku1mRyxRdPxy_WuWcuGZow,Cri2Green +UCl2mFZoRqjw_ELax4Yisf6w,http://www.youtube.com/channel/UCl2mFZoRqjw_ELax4Yisf6w,Louis Rossmann +UClOTXvBhNkVce1J8gsXncAw,http://www.youtube.com/channel/UClOTXvBhNkVce1J8gsXncAw,8choPlay +UCl_VgfLer0xGmMnHiUaXIiw,http://www.youtube.com/channel/UCl_VgfLer0xGmMnHiUaXIiw,Amanda Parot +UCld68syR8Wi-GY_n4CaoJGA,http://www.youtube.com/channel/UCld68syR8Wi-GY_n4CaoJGA,Brodie Robertson +UClv_RjOAHch27mb_Yy-6zzQ,http://www.youtube.com/channel/UClv_RjOAHch27mb_Yy-6zzQ,RodSquare +UCmOiT9ZnSvLZ1XMBFjGiNmQ,http://www.youtube.com/channel/UCmOiT9ZnSvLZ1XMBFjGiNmQ,‌ +UCmS75G-98QihSusY7NfCZtw,http://www.youtube.com/channel/UCmS75G-98QihSusY7NfCZtw,Bizarrap +UCmb0LnmFYceH7toqgmUTJDA,http://www.youtube.com/channel/UCmb0LnmFYceH7toqgmUTJDA,Vandal +UCnTB8uNxND40iwvPiyzpnWw,http://www.youtube.com/channel/UCnTB8uNxND40iwvPiyzpnWw,heiakim +UCnXKG-SXe58CDCwxltHYQZg,http://www.youtube.com/channel/UCnXKG-SXe58CDCwxltHYQZg,ً +UCngOaQh68FzLqK1LqoUs0rw,http://www.youtube.com/channel/UCngOaQh68FzLqK1LqoUs0rw,NIGHTPLEASE YT +UCnq5UPLhlAEttKl8sszFmvw,http://www.youtube.com/channel/UCnq5UPLhlAEttKl8sszFmvw,Hartvigen +UCo5HJNjfdSoPWsdAHLsvSxQ,http://www.youtube.com/channel/UCo5HJNjfdSoPWsdAHLsvSxQ,ShaunTrack +UCo8bcnLyZH8tBIH9V1mLgqQ,http://www.youtube.com/channel/UCo8bcnLyZH8tBIH9V1mLgqQ,TheOdd1sOut +UCoGBPBXyq28cE4g2TaB6lRQ,http://www.youtube.com/channel/UCoGBPBXyq28cE4g2TaB6lRQ,Damian Kuc +UCocHtA1ADT6kTObipYzJoww,http://www.youtube.com/channel/UCocHtA1ADT6kTObipYzJoww,SunlessKhan +UCpFFItkfZz1qz5PpHpqzYBw,http://www.youtube.com/channel/UCpFFItkfZz1qz5PpHpqzYBw,Nexpo +UCpUcgGF1nS6rp6-L9OWfbTw,http://www.youtube.com/channel/UCpUcgGF1nS6rp6-L9OWfbTw,woofnion +UCppX-xsYa-PTE09GAoFCo3Q,http://www.youtube.com/channel/UCppX-xsYa-PTE09GAoFCo3Q,MrCarlosnoob +UCq6VFHwMzcMXbuKyG7SQYIg,http://www.youtube.com/channel/UCq6VFHwMzcMXbuKyG7SQYIg,penguinz0 +UCq6aw03lNILzV96UvEAASfQ,http://www.youtube.com/channel/UCq6aw03lNILzV96UvEAASfQ,bill wurtz +UCqW41JX8pX9yJxfQ9cEXUEQ,http://www.youtube.com/channel/UCqW41JX8pX9yJxfQ9cEXUEQ,Atto Usagi +UCqbkm47qBxDj-P3lI9voIAw,http://www.youtube.com/channel/UCqbkm47qBxDj-P3lI9voIAw,danooct1 +UCrK0KR016cpACKJ5jyy0MwQ,http://www.youtube.com/channel/UCrK0KR016cpACKJ5jyy0MwQ,BocaSur +UCrYJpl5oC2ItVLPub5LVpWw,http://www.youtube.com/channel/UCrYJpl5oC2ItVLPub5LVpWw,Alecmolon +UCs2zbzxnFcilW4-2_yTXz-g,http://www.youtube.com/channel/UCs2zbzxnFcilW4-2_yTXz-g,SupraNews +UCsHaCORAuXxrMDzC0hr8GPw,http://www.youtube.com/channel/UCsHaCORAuXxrMDzC0hr8GPw,MxR Plays +UCsNqS-jSSdFRIvwmyd1nEVA,http://www.youtube.com/channel/UCsNqS-jSSdFRIvwmyd1nEVA,Sonter +UCsh1ax5Xk75-qjSGKVWxRDw,http://www.youtube.com/channel/UCsh1ax5Xk75-qjSGKVWxRDw,Nickel Firepower +UCsmx517WjzoyFuC17DGVt1A,http://www.youtube.com/channel/UCsmx517WjzoyFuC17DGVt1A,Wismichu +UCsvyYvzEG4YSuqxka__tkKQ,http://www.youtube.com/channel/UCsvyYvzEG4YSuqxka__tkKQ,Kalathras Zone +UCt-GOpCw4dOBlIyqL9A1ztA,http://www.youtube.com/channel/UCt-GOpCw4dOBlIyqL9A1ztA,Sr Pelo +UCt-dxEVG45pFcqtbOpXefBg,http://www.youtube.com/channel/UCt-dxEVG45pFcqtbOpXefBg,Sammwy +UCtMVHI3AJD4Qk4hcbZnI9ZQ,http://www.youtube.com/channel/UCtMVHI3AJD4Qk4hcbZnI9ZQ,SomeOrdinaryGamers +UCtOKlTUZUw41b1rz8t5ll1g,http://www.youtube.com/channel/UCtOKlTUZUw41b1rz8t5ll1g,TecnoSmart +UCtb8P4rf_1n8KS8eZk_lNNw,http://www.youtube.com/channel/UCtb8P4rf_1n8KS8eZk_lNNw,Fitz +UCtbN9AcvV2VndFQDTuahwtQ,http://www.youtube.com/channel/UCtbN9AcvV2VndFQDTuahwtQ,TENGU +UCu7TZ_ATWgjgD9IrNLdnYDA,http://www.youtube.com/channel/UCu7TZ_ATWgjgD9IrNLdnYDA,Little Big +UCv0jmyEcRipJqGV2i-P2jhA,http://www.youtube.com/channel/UCv0jmyEcRipJqGV2i-P2jhA,SHUNchan +UCv1Kcz-CuGM6mxzL3B1_Eiw,http://www.youtube.com/channel/UCv1Kcz-CuGM6mxzL3B1_Eiw,Gardiner Bryant +UCv6P5nsS9rP4tDtFlqLU_QQ,http://www.youtube.com/channel/UCv6P5nsS9rP4tDtFlqLU_QQ,ずっと真夜中でいいのに。 +UCvRihiQZiIO9RH2HOylfQ0Q,http://www.youtube.com/channel/UCvRihiQZiIO9RH2HOylfQ0Q,Snuffy +UCvnNkJsbONVhdXuHY_LpsdA,http://www.youtube.com/channel/UCvnNkJsbONVhdXuHY_LpsdA,ElRichMC - Minecraft & Gaming a otro nivel +UCw7CjJ7lFfpEC5FKmIS1ySw,http://www.youtube.com/channel/UCw7CjJ7lFfpEC5FKmIS1ySw,‏‏‎ +UCwGX2cE21VPBEJ49hcprP9w,http://www.youtube.com/channel/UCwGX2cE21VPBEJ49hcprP9w,SupraPixel +UCwNzCh6frugZA_cJk1LDUgA,http://www.youtube.com/channel/UCwNzCh6frugZA_cJk1LDUgA,Moai Gr +UCwi7FK3XDDwhioq1hV8Zmqg,http://www.youtube.com/channel/UCwi7FK3XDDwhioq1hV8Zmqg,La Zona Cero +UCwykB5130Xf1ck1T4zgG-nA,http://www.youtube.com/channel/UCwykB5130Xf1ck1T4zgG-nA,SLost +UCzFJ9ZkjYpxWTSqX9cVXj4g,http://www.youtube.com/channel/UCzFJ9ZkjYpxWTSqX9cVXj4g,tomiii 11 +UCzVn5MukMsABSfWktHaw-qw,http://www.youtube.com/channel/UCzVn5MukMsABSfWktHaw-qw,Soron +UCzXwjTI6c6mVn6oui_p6oiw,http://www.youtube.com/channel/UCzXwjTI6c6mVn6oui_p6oiw,SMii7Y + diff --git a/extractor/src/test/resources/youtube_takeout_import_test_2.zip b/extractor/src/test/resources/youtube_takeout_import_test_2.zip new file mode 100644 index 0000000000000000000000000000000000000000..6ec565c7c3c681bfaa9f0e9381496b641e30f7bd GIT binary patch literal 5690 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