From ad985807c947a1302afe3861bd4d757c650f90fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 19 May 2024 21:25:36 -0300 Subject: [PATCH 01/85] Accept only one API key on __init__ --- youtool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/youtool.py b/youtool.py index dc90657..f60780c 100644 --- a/youtool.py +++ b/youtool.py @@ -355,6 +355,8 @@ class YouTube: } def __init__(self, api_keys: List[str], disable_ipv6=False): + if isinstance(api_keys, str): # Just one API key was passed + api_keys = [api_keys] self.__api_keys = list(api_keys) # Consume and make a copy (it'll be `pop`ed) self.__current_key = self.__api_keys.pop(0) self.__params = {"key": self.__current_key, "maxResults": 50} # 50 is the max for YouTube Data API v3 From 51de526cc90704551fdf800b40cf6fed3cee53f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Mon, 8 Jul 2024 10:22:15 -0300 Subject: [PATCH 02/85] Move module to a directory --- Makefile | 2 +- setup.cfg | 2 +- tests.py => tests/test_YouTube.py | 0 youtool.py => youtool/__init__.py | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename tests.py => tests/test_YouTube.py (100%) rename youtool.py => youtool/__init__.py (100%) diff --git a/Makefile b/Makefile index ecf8f5a..572a795 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ shell: docker compose run --rm -it main bash test: - docker compose run --rm main pytest -o cache_dir=/app/.pytest-cache -svvv --pdb --doctest-modules youtool.py tests.py + docker compose run --rm main pytest -o cache_dir=/app/.pytest-cache -svvv --pdb --doctest-modules youtool/ tests/ test-release: docker compose run --rm main /app/release.sh --test diff --git a/setup.cfg b/setup.cfg index efdb5d9..b8d3b2d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,7 +20,7 @@ classifiers = [options] include_package_data = true -py_modules = youtool +packages = find: python_requires = >=3.7 install_requires = file: requirements/base.txt diff --git a/tests.py b/tests/test_YouTube.py similarity index 100% rename from tests.py rename to tests/test_YouTube.py diff --git a/youtool.py b/youtool/__init__.py similarity index 100% rename from youtool.py rename to youtool/__init__.py From 53ef41766c8e5d9ea5780cab800757744712e40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Mon, 8 Jul 2024 10:26:58 -0300 Subject: [PATCH 03/85] Remove unneeded `version` on compose --- compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/compose.yml b/compose.yml index f310f24..cf59e9a 100644 --- a/compose.yml +++ b/compose.yml @@ -1,5 +1,3 @@ -version: "3.8" - services: main: env_file: ".env" From 476d9c43f6a0ecfce931fd03162b2976a8a44095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Mon, 8 Jul 2024 23:32:51 -0300 Subject: [PATCH 04/85] Fix docker/compose configs --- Dockerfile | 2 +- compose.yml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8e78a3f..9b88ea5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /app RUN apt update \ && apt upgrade -y \ - && apt install -y build-essential make python3-dev \ + && apt install -y build-essential ffmpeg make python3-dev \ && apt purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && apt clean \ && rm -rf /var/lib/apt/lists/* diff --git a/compose.yml b/compose.yml index cf59e9a..553ef5b 100644 --- a/compose.yml +++ b/compose.yml @@ -9,5 +9,3 @@ services: user: "${UID}:${GID}" volumes: - ${PWD}:/app - ports: - - 5000:5000 From a24fa8da96c2dd35e8496a5e5b0f8a0a7daf4f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Mon, 8 Jul 2024 23:32:58 -0300 Subject: [PATCH 05/85] Fix hard-coded values in tests --- tests/test_YouTube.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_YouTube.py b/tests/test_YouTube.py index 1e76322..977edb4 100644 --- a/tests/test_YouTube.py +++ b/tests/test_YouTube.py @@ -186,7 +186,7 @@ def test_YouTube_video_comments(): def test_YouTube_video_livechat(): video_livechat_data = list(yt.video_livechat(live_video_id)) assert_types("live_comment", expected_live_comment_types, video_livechat_data) - assert len(video_livechat_data) == 589 + assert len(video_livechat_data) > 500 assert sum(video["money_amount"] for video in video_livechat_data if video["money_currency"] == "BRL") == Decimal( "70.9" ) @@ -233,5 +233,5 @@ def test_YouTube_video_search(): video_category_id=None, ) ) - assert len(video_search_data) == 6 + assert len(video_search_data) > 5 assert_types("video from search", expected_video_types, video_search_data) From 464c668037b25338938691da3070c9d336a68bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Mon, 8 Jul 2024 23:53:06 -0300 Subject: [PATCH 06/85] Implement `utils.simplify_vtt` --- requirements/transcription.txt | 2 +- tests/data/whisper-clean-hd-notebook.vtt | 326 ++ .../data/whisper-clean-limpeza-nespresso.vtt | 332 ++ tests/data/whisper-words-hd-notebook.vtt | 3797 +++++++++++++++++ .../data/whisper-words-limpeza-nespresso.vtt | 1916 +++++++++ tests/data/youtube-auto-hd-notebook.vtt | 1288 ++++++ tests/data/youtube-auto-limpeza-nespresso.vtt | 824 ++++ tests/test_utils.py | 65 + youtool/utils.py | 41 + 9 files changed, 8590 insertions(+), 1 deletion(-) create mode 100644 tests/data/whisper-clean-hd-notebook.vtt create mode 100644 tests/data/whisper-clean-limpeza-nespresso.vtt create mode 100644 tests/data/whisper-words-hd-notebook.vtt create mode 100644 tests/data/whisper-words-limpeza-nespresso.vtt create mode 100644 tests/data/youtube-auto-hd-notebook.vtt create mode 100644 tests/data/youtube-auto-limpeza-nespresso.vtt create mode 100644 tests/test_utils.py create mode 100644 youtool/utils.py diff --git a/requirements/transcription.txt b/requirements/transcription.txt index abb4e49..47e3da8 100644 --- a/requirements/transcription.txt +++ b/requirements/transcription.txt @@ -1,2 +1,2 @@ +webvtt-py yt-dlp - diff --git a/tests/data/whisper-clean-hd-notebook.vtt b/tests/data/whisper-clean-hd-notebook.vtt new file mode 100644 index 0000000..c1d433c --- /dev/null +++ b/tests/data/whisper-clean-hd-notebook.vtt @@ -0,0 +1,326 @@ +WEBVTT + +00:01.520 --> 00:06.900 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:07.660 --> 00:10.480 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:11.200 --> 00:14.480 +e muitas vezes quando o sistema não está iniciando + +00:14.480 --> 00:20.080 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:20.520 --> 00:23.500 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:23.500 --> 00:27.440 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:27.440 --> 00:30.360 +e o HD já esteja nos dias finais de vida dele, + +00:30.360 --> 00:32.220 +mas isso não quer dizer que você perdeu os dados, + +00:32.880 --> 00:38.220 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:38.220 --> 00:41.380 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:42.120 --> 00:46.700 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:46.700 --> 00:49.960 +num outro computador, copiar os dados e formatar o HD. + +00:50.680 --> 00:54.120 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:54.440 --> 00:59.080 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:59.960 --> 01:02.000 +queira fazer um backup, só lembre o seguinte, + +01:02.160 --> 01:07.940 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:07.940 --> 01:13.360 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:13.920 --> 01:19.140 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:20.940 --> 01:23.100 +Essa case aqui eu comprei no Mercado Livre, + +01:23.400 --> 01:26.500 +vou ver se eu deixo um link na descrição para compra, + +01:26.500 --> 01:31.300 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:31.380 --> 01:33.460 +inclusive com a chave, então tá escuta aqui. + +01:34.180 --> 01:37.220 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:39.760 --> 01:43.400 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:43.880 --> 01:45.440 +então aqui já vem a tampinha da case, + +01:46.900 --> 01:51.440 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:51.860 --> 01:55.780 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:55.780 --> 02:01.640 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +02:02.640 --> 02:07.500 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:08.000 --> 02:11.780 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:12.360 --> 02:16.240 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:16.980 --> 02:21.780 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:21.780 --> 02:25.300 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:26.840 --> 02:31.580 +Eu vou então começar, basicamente é muito simples o processo, + +02:32.200 --> 02:35.620 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:35.820 --> 02:37.520 +então tem um conector menor e um maior, + +02:38.220 --> 02:39.820 +olhando aqui para o HD tem um menor e um maior, + +02:40.280 --> 02:43.200 +então tem que virar aqui para encaixar direitinho, + +02:43.960 --> 02:50.880 +daí eu encaixo e agora é só fechar aqui a case. + +02:50.880 --> 02:55.640 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:55.780 --> 02:59.400 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:59.940 --> 03:03.740 +colocar o HD dentro da case e fechar a case. + +03:04.060 --> 03:06.400 +Não tem muito mais segredo além disso não. + +03:07.220 --> 03:10.920 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:10.920 --> 03:18.780 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:18.780 --> 03:22.060 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:23.180 --> 03:26.020 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:26.120 --> 03:31.120 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:31.840 --> 03:33.980 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:34.500 --> 03:37.680 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:40.930 --> 03:44.710 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:47.110 --> 03:49.510 +Daí qualquer sistema operacional que você esteja utilizando, + +03:49.610 --> 03:53.590 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:54.250 --> 03:58.410 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:58.890 --> 04:04.150 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:04.950 --> 04:07.870 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:08.490 --> 04:10.890 +e aí por isso você quer recuperar os arquivos, + +04:10.890 --> 04:14.330 +cuidado que no Windows ele costuma sugerir formatar, + +04:14.810 --> 04:17.390 +dado que ele não consegue as vezes identificar a partição. + +04:18.030 --> 04:20.670 +Não faça isso, se você formatar você vai perder os seus dados, + +04:21.530 --> 04:27.230 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:27.690 --> 04:31.890 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:32.470 --> 04:35.970 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:36.450 --> 04:38.890 +que só tem um conector, conectar aqui, + +04:38.890 --> 04:43.290 +e aí vai ser só ligar no computador. + +04:44.430 --> 04:45.690 +Bom, estou aqui no computador agora, + +04:46.650 --> 04:49.530 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:49.530 --> 04:52.610 +e o meu gerenciador de janelas é o i3 da Bram, + +04:52.730 --> 04:56.250 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:56.770 --> 05:01.630 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +05:03.610 --> 05:07.390 +e deve aparecer aqui do ladinho o novo volume, + +05:07.390 --> 05:09.610 +que seria o correspondente ao HD. + +05:10.410 --> 05:12.970 +Está lá, é um volume de 500 gigabytes, + +05:13.830 --> 05:16.210 +que é o HD que eu tirei do notebook. + +05:16.750 --> 05:19.550 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:19.670 --> 05:24.190 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:24.830 --> 05:27.790 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:27.910 --> 05:31.030 +essa case que eu comprei é feita para HD de notebook, + +05:31.190 --> 05:34.710 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:34.710 --> 05:36.670 +é um HD de 3,5 polegadas. + +05:36.670 --> 05:40.250 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:40.330 --> 05:43.690 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:43.890 --> 05:48.190 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:48.450 --> 05:51.450 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:51.870 --> 05:54.590 +e aqui tem todos os downloads, documentos e tudo mais. + +05:55.170 --> 05:58.390 +Se esse HD fosse de um sistema Windows, + +05:59.250 --> 06:02.010 +provavelmente você entraria aqui na pasta usuários, + +06:02.630 --> 06:06.870 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:07.770 --> 06:09.570 +Mas basicamente é esse o processo. + +06:10.050 --> 06:12.810 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:13.030 --> 06:16.070 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:16.650 --> 06:21.110 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:22.170 --> 06:26.170 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:26.170 --> 06:31.250 +e depois disso eu vou então poder usar, tranquilamente, + +06:32.090 --> 06:34.810 +esse HD externo para copiar dados e tudo mais. + +06:35.350 --> 06:36.390 +Então a ideia é essa. + +06:36.490 --> 06:40.670 +Eu vou fazer essa formatação para que seja irrecuperável + +06:40.670 --> 06:43.650 +o que está aqui em termos de passado dos dados, + +06:44.270 --> 06:47.510 +e depois eu vou utilizar normalmente o HD. + +06:47.910 --> 06:50.190 +Eu vou também, na verdade, antes de formatar, + +06:50.590 --> 06:53.170 +passar por um programa que detecta bad blocks, + +06:53.170 --> 06:58.210 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:58.810 --> 07:01.570 +e aí depois eu vou utilizá-lo novamente. + +07:01.830 --> 07:07.710 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:08.150 --> 07:11.450 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:11.610 --> 07:14.870 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:15.970 --> 07:17.990 +É isso. Espero que vocês tenham gostado aí. + +07:18.370 --> 07:19.890 +Qualquer dúvida, pode deixar nos comentários. + diff --git a/tests/data/whisper-clean-limpeza-nespresso.vtt b/tests/data/whisper-clean-limpeza-nespresso.vtt new file mode 100644 index 0000000..5182d3f --- /dev/null +++ b/tests/data/whisper-clean-limpeza-nespresso.vtt @@ -0,0 +1,332 @@ +WEBVTT + +00:00.910 --> 00:04.170 +Temos aqui uma cafeteira Nespresso Inicia + +00:04.910 --> 00:08.850 +que não inicia a água. + +00:09.110 --> 00:13.410 +Vou puxar a água, vou ligar aqui, + +00:15.700 --> 00:17.080 +dar um tempinho para esquentar. + +00:17.680 --> 00:20.840 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:21.600 --> 00:26.420 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:27.520 --> 00:31.640 +ela acabou secando, ficou ar na tubulação + +00:31.640 --> 00:35.680 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:35.680 --> 00:37.260 +porque só tem ar na tubulação + +00:37.920 --> 00:41.340 +e aí ela acaba não conseguindo puxar. + +00:41.540 --> 00:43.620 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:44.480 --> 00:48.040 +Vou tirar aqui a bandejinha, + +00:48.740 --> 00:50.920 +chegar lá para frente para poder cair na pia. + +00:51.500 --> 00:51.920 +Vamos lá. + +00:53.790 --> 01:05.190 +Não tem cápsula, não sai uma gota de ar. + +01:10.760 --> 01:12.840 +E aqui também não puxa nada. + +01:15.720 --> 01:20.540 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:20.540 --> 01:23.220 +toda a água de uma vez só por ali. + +01:23.460 --> 01:25.340 +Tem que segurar os dois botões por 5 segundos + +01:26.460 --> 01:27.680 +até eles piscarem. + +01:31.300 --> 01:33.020 +Começou a piscar rápido. + +01:33.960 --> 01:35.760 +Não é aquela piscada de quando ela liga, + +01:35.840 --> 01:36.720 +que ela está aquecendo. + +01:37.420 --> 01:38.540 +Aperta qualquer um dos dois + +01:39.440 --> 01:40.980 +e ela vai começar a trabalhar + +01:41.480 --> 01:43.600 +até que toda essa água seja... + +01:45.020 --> 01:46.620 +passe pelo sistema interno dela. + +01:58.660 --> 02:00.240 +Ainda sem nada. + +02:02.790 --> 02:06.090 +Parece realmente ser problema de ar ali dentro. + +02:07.110 --> 02:08.030 +E a solução que eu vi, + +02:08.470 --> 02:09.810 +que algumas pessoas fizeram, + +02:10.330 --> 02:12.750 +foi injetar água aqui com uma seringa + +02:12.750 --> 02:14.170 +para poder entrar com pressão + +02:14.730 --> 02:16.310 +e aí com isso ela consegue + +02:16.910 --> 02:18.470 +passar pela camada de ar + +02:18.470 --> 02:21.350 +e a máquina consegue continuar o ciclo. + +02:22.490 --> 02:23.970 +Como eu não tenho uma seringa aqui, + +02:25.370 --> 02:26.490 +eu vou tentar simplesmente + +02:28.510 --> 02:31.010 +ter certeza de que eu tenho água + +02:31.010 --> 02:32.190 +aqui nessa parte, + +02:33.890 --> 02:35.310 +um pouquinho aqui com a leiteira, + +02:40.420 --> 02:42.940 +ter certeza de que tem água chegando ali + +02:43.540 --> 02:44.940 +e daí eu vou apertar o botão + +02:45.840 --> 02:47.460 +para ela começar a puxar. + +02:47.860 --> 02:49.800 +Vou tentar dar umas apertadas aqui + +02:49.800 --> 02:51.820 +para ver se ela consegue... + +02:51.820 --> 02:53.700 +a água consegue entrar ali no circuito. + +03:00.250 --> 03:00.630 +Puxou. + +03:01.930 --> 03:02.870 +Puxou a água. + +03:03.070 --> 03:04.170 +Ainda não saiu por aqui, + +03:05.690 --> 03:06.970 +mas agora eu acredito que + +03:08.250 --> 03:09.190 +colocando aqui + +03:09.850 --> 03:11.490 +ela conseguiu continuar o processo. + +03:14.480 --> 03:16.600 +Está lá, funcionando. + +03:18.560 --> 03:20.260 +Agora é só fazer uma limpezinha nela + +03:21.420 --> 03:23.200 +e voltar a tomar os cafés. + +03:24.080 --> 03:26.040 +O processo de limpeza aqui + +03:26.740 --> 03:29.560 +desse bico das cafeteiras Nespresso, + +03:29.660 --> 03:31.740 +que às vezes pode acumular aqui + +03:31.740 --> 03:33.440 +um pouco de borra de café + +03:33.440 --> 03:35.600 +e aí o fluxo sai menor. + +03:36.040 --> 03:38.480 +Ela acaba vazando mais aqui para dentro. + +03:39.100 --> 03:41.000 +A água da bandeja enche rápido. + +03:41.500 --> 03:42.620 +E nem sempre + +03:42.620 --> 03:44.440 +só passar água por ela + +03:44.440 --> 03:45.780 +sem a cápsula resolve. + +03:46.340 --> 03:48.040 +Tem muita coisa que fica impregnada aqui. + +03:48.660 --> 03:49.520 +Uma maneira que eu vi, + +03:49.740 --> 03:51.400 +que eu inclusive achei no YouTube, + +03:52.160 --> 03:54.660 +é o seguinte, tira aqui o reservatório, + +03:54.840 --> 03:55.680 +tira toda a parte aqui, + +03:55.820 --> 03:56.840 +não tem cápsula aqui dentro, + +03:56.940 --> 03:57.600 +deixa ela aberta, + +03:58.620 --> 04:01.440 +você vai virar ela de cabeça para baixo, + +04:02.460 --> 04:03.480 +se eu consigo mostrar aqui, + +04:04.540 --> 04:06.000 +inclinar para frente, + +04:06.340 --> 04:07.760 +ou seja, não é para ela ficar + +04:07.760 --> 04:09.740 +na posição totalmente aqui + +04:09.740 --> 04:11.740 +em 90 graus, + +04:11.800 --> 04:13.960 +é para uma leve inclinação na frente, + +04:14.160 --> 04:16.200 +porque a gente vai jogar água + +04:16.200 --> 04:17.080 +com a torneira, + +04:17.740 --> 04:18.760 +se possível água quente, + +04:18.920 --> 04:20.700 +com a maior pressão possível, aqui no bico. + +04:21.580 --> 04:24.120 +E aí a água vai passar por dentro do bico + +04:24.120 --> 04:25.820 +e sair ali por cima. + +04:26.880 --> 04:28.000 +É importante inclinar + +04:28.340 --> 04:29.460 +justamente para que a água + +04:29.460 --> 04:31.940 +não entre na máquina, + +04:32.080 --> 04:33.920 +para que ela caia ali na pia. + +04:34.620 --> 04:36.400 +Então, estou com a máquina aqui, + +04:37.140 --> 04:38.240 +vou inclinar, + +04:39.380 --> 04:40.280 +água quente, + +04:41.380 --> 04:43.060 +na maior pressão possível, + +05:11.460 --> 05:12.760 +esperar escorrer tudo, + +05:15.860 --> 05:17.300 +e daí podemos virar novamente + +05:17.300 --> 05:18.300 +a nossa máquina. + +05:18.860 --> 05:20.340 +Esse procedimento também funciona + +05:20.340 --> 05:22.000 +com outras máquinas Nespresso, + +05:22.100 --> 05:23.060 +com esse tipo de cápsula, + +05:23.680 --> 05:25.700 +e fica aqui a dica para quem quiser comprar + +05:25.700 --> 05:27.700 +cápsulas de café especial, + +05:27.880 --> 05:30.140 +micro lotes, em vários tipos, + +05:30.740 --> 05:32.640 +entra aqui no site do Crio Café, + +05:32.640 --> 05:35.220 +que é uma cafeteria e refação de São Paulo, + +05:35.360 --> 05:36.760 +mas que entrega para todo o Brasil, + +05:37.300 --> 05:38.700 +e tem inclusive assinatura, + +05:38.860 --> 05:40.920 +café com cápsula, café em grão, moído, + +05:41.580 --> 05:43.040 +e um monte de outras coisas gostosas. + +05:43.380 --> 05:43.700 +Valeu! + diff --git a/tests/data/whisper-words-hd-notebook.vtt b/tests/data/whisper-words-hd-notebook.vtt new file mode 100644 index 0000000..8826aa7 --- /dev/null +++ b/tests/data/whisper-words-hd-notebook.vtt @@ -0,0 +1,3797 @@ +WEBVTT + +00:01.520 --> 00:02.020 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:02.020 --> 00:02.080 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:02.080 --> 00:02.260 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:02.260 --> 00:02.540 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:02.540 --> 00:02.760 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:02.760 --> 00:03.120 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:03.120 --> 00:03.620 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:03.620 --> 00:03.880 +Bom, muita gente não sabe que para usar o HD do notebook como um HD externo, + +00:03.880 --> 00:04.000 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:04.000 --> 00:04.200 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:04.200 --> 00:04.340 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:04.340 --> 00:04.560 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:04.560 --> 00:04.720 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:04.720 --> 00:05.220 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:05.220 --> 00:05.580 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:05.580 --> 00:06.140 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:06.140 --> 00:06.400 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:06.400 --> 00:06.900 +Bom, muita gente não sabe que dá para usar o HD do notebook como um HD externo, + +00:07.660 --> 00:07.760 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:07.760 --> 00:07.960 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:07.960 --> 00:08.200 +na verdade para usar o HD do desktop também como um HD externo, + +00:08.200 --> 00:08.300 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:08.300 --> 00:08.420 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:08.420 --> 00:08.560 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:08.560 --> 00:08.700 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:08.700 --> 00:08.880 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:08.880 --> 00:09.160 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:09.160 --> 00:09.520 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:09.520 --> 00:09.720 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:09.720 --> 00:09.840 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:09.840 --> 00:10.020 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:10.020 --> 00:10.480 +na verdade dá para usar o HD do desktop também como um HD externo, + +00:11.200 --> 00:11.660 +e muitas vezes quando o sistema não está iniciando + +00:11.660 --> 00:12.100 +e muitas vezes quando o sistema não está iniciando + +00:12.100 --> 00:12.460 +e muitas vezes quando o sistema não está iniciando + +00:12.460 --> 00:12.800 +e muitas vezes quando o sistema não está iniciando + +00:12.800 --> 00:13.000 +e muitas vezes quando o sistema não está iniciando + +00:13.000 --> 00:13.340 +e muitas vezes quando o sistema não está iniciando + +00:13.340 --> 00:13.580 +e muitas vezes quando o sistema não está iniciando + +00:13.580 --> 00:13.800 +e muitas vezes quando o sistema não está iniciando + +00:13.800 --> 00:14.480 +e muitas vezes quando o sistema não está iniciando + +00:14.480 --> 00:15.260 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:15.260 --> 00:16.040 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:16.040 --> 00:16.700 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:16.700 --> 00:17.040 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:17.040 --> 00:17.220 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:17.220 --> 00:17.520 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:17.520 --> 00:17.720 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:17.720 --> 00:17.980 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:17.980 --> 00:18.620 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:18.620 --> 00:19.340 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:19.340 --> 00:20.080 +é provavelmente um problema do HD, existem obviamente outras possibilidades, + +00:20.520 --> 00:20.660 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:20.660 --> 00:20.840 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:20.840 --> 00:21.080 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:21.080 --> 00:21.340 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:21.340 --> 00:21.500 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:21.500 --> 00:21.560 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:21.560 --> 00:21.660 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:21.660 --> 00:21.840 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:21.840 --> 00:21.920 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:21.920 --> 00:22.020 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:22.020 --> 00:22.140 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:22.140 --> 00:22.320 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:22.320 --> 00:22.700 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:22.700 --> 00:22.920 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:22.920 --> 00:23.100 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:23.100 --> 00:23.500 +mas quando você liga lá, por exemplo, que o Windows trava na tela inicial + +00:23.500 --> 00:24.180 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:24.180 --> 00:24.400 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:24.400 --> 00:24.720 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:24.720 --> 00:25.280 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:25.280 --> 00:25.460 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:25.460 --> 00:25.620 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:25.620 --> 00:25.740 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:25.740 --> 00:25.940 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:25.940 --> 00:26.320 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:26.320 --> 00:26.560 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:26.560 --> 00:26.780 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:26.780 --> 00:27.440 +e nunca anda, pode ser que algum arquivo tenha sido corrompido + +00:27.440 --> 00:27.920 +e o HD já esteja nos dias finais de vida dele, + +00:27.920 --> 00:28.020 +e o HD já esteja nos dias finais de vida dele, + +00:28.020 --> 00:28.240 +e o HD já esteja nos dias finais de vida dele, + +00:28.240 --> 00:28.460 +e o HD esteja nos dias finais de vida dele, + +00:28.460 --> 00:28.840 +e o HD já esteja nos dias finais de vida dele, + +00:28.840 --> 00:29.060 +e o HD já esteja nos dias finais de vida dele, + +00:29.060 --> 00:29.300 +e o HD já esteja nos dias finais de vida dele, + +00:29.300 --> 00:29.660 +e o HD já esteja nos dias finais de vida dele, + +00:29.660 --> 00:29.840 +e o HD já esteja nos dias finais de vida dele, + +00:29.840 --> 00:30.040 +e o HD já esteja nos dias finais de vida dele, + +00:30.040 --> 00:30.360 +e o HD já esteja nos dias finais de vida dele, + +00:30.360 --> 00:30.740 +mas isso não quer dizer que você perdeu os dados, + +00:30.740 --> 00:30.900 +mas isso não quer dizer que você perdeu os dados, + +00:30.900 --> 00:31.040 +mas isso não quer dizer que você perdeu os dados, + +00:31.040 --> 00:31.160 +mas isso não quer dizer que você perdeu os dados, + +00:31.160 --> 00:31.260 +mas isso não quer dizer que você perdeu os dados, + +00:31.260 --> 00:31.380 +mas isso não quer dizer que você perdeu os dados, + +00:31.380 --> 00:31.480 +mas isso não quer dizer que você perdeu os dados, + +00:31.480 --> 00:31.800 +mas isso não quer dizer que você perdeu os dados, + +00:31.800 --> 00:31.940 +mas isso não quer dizer que você perdeu os dados, + +00:31.940 --> 00:32.220 +mas isso não quer dizer que você perdeu os dados, + +00:32.880 --> 00:33.340 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:33.340 --> 00:33.660 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:33.660 --> 00:33.980 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:33.980 --> 00:34.140 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:34.140 --> 00:34.360 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:34.360 --> 00:34.920 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:34.920 --> 00:35.500 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:35.500 --> 00:35.580 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:35.580 --> 00:35.940 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:35.940 --> 00:36.300 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:36.300 --> 00:36.660 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:36.660 --> 00:36.900 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:36.900 --> 00:37.360 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:37.360 --> 00:38.220 +você pode tirar o HD, conectá-lo numa case dessa de HD externo + +00:38.220 --> 00:38.820 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:38.820 --> 00:38.980 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:38.980 --> 00:39.240 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:39.240 --> 00:39.420 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:39.420 --> 00:39.740 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:39.740 --> 00:39.860 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:39.860 --> 00:39.940 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:39.940 --> 00:39.960 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:39.960 --> 00:40.020 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:40.020 --> 00:40.240 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:40.240 --> 00:40.380 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:40.380 --> 00:40.600 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:40.600 --> 00:40.820 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:40.820 --> 00:41.380 +para o HD de notebook, que é o HD de 2,5 polegadas, + +00:42.120 --> 00:42.220 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:42.220 --> 00:42.460 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:42.460 --> 00:43.420 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:43.420 --> 00:43.800 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:43.800 --> 00:44.340 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:44.340 --> 00:44.640 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:44.640 --> 00:44.920 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:44.920 --> 00:45.200 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:45.200 --> 00:45.560 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:45.560 --> 00:45.800 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:45.800 --> 00:45.900 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:45.900 --> 00:46.020 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:46.020 --> 00:46.180 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:46.180 --> 00:46.700 +e daí você consegue ligar esse HD via USB como se fosse um pendrive + +00:46.700 --> 00:46.940 +num outro computador, copiar os dados e formatar o HD. + +00:46.940 --> 00:47.120 +num outro computador, copiar os dados e formatar o HD. + +00:47.120 --> 00:47.560 +num outro computador, copiar os dados e formatar o HD. + +00:47.560 --> 00:48.100 +num outro computador, copiar os dados e formatar o HD. + +00:48.100 --> 00:48.380 +num outro computador, copiar os dados e formatar o HD. + +00:48.380 --> 00:48.560 +num outro computador, copiar os dados e formatar o HD. + +00:48.560 --> 00:48.880 +num outro computador, copiar os dados e formatar o HD. + +00:48.880 --> 00:49.280 +num outro computador, copiar os dados e formatar o HD. + +00:49.280 --> 00:49.660 +num outro computador, copiar os dados e formatar o HD. + +00:49.660 --> 00:49.760 +num outro computador, copiar os dados e formatar o HD. + +00:49.760 --> 00:49.960 +num outro computador, copiar os dados e formatar o HD. + +00:50.680 --> 00:50.900 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:50.900 --> 00:51.540 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:51.540 --> 00:51.660 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:51.660 --> 00:51.780 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:51.780 --> 00:52.060 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:52.060 --> 00:52.360 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:52.360 --> 00:53.200 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:53.200 --> 00:53.340 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:53.340 --> 00:53.520 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:53.520 --> 00:53.700 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:53.700 --> 00:54.120 +Então essa é uma dica para reaproveitar o HD de notebook, + +00:54.440 --> 00:54.700 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:54.700 --> 00:55.020 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:55.020 --> 00:55.440 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:55.440 --> 00:55.940 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:55.940 --> 00:56.280 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:56.280 --> 00:56.460 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:56.460 --> 00:56.600 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:56.600 --> 00:56.880 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:56.880 --> 00:57.000 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:57.000 --> 00:57.180 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:57.180 --> 00:57.400 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:57.400 --> 00:57.760 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:57.760 --> 00:58.520 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:58.520 --> 00:58.640 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:58.640 --> 00:59.080 +seja porque você não consegue abrir o sistema, seja porque você trocou de computador, + +00:59.960 --> 01:00.440 +queira fazer um backup, só lembre o seguinte, + +01:00.440 --> 01:00.640 +queira fazer um backup, só lembre o seguinte, + +01:00.640 --> 01:00.740 +queira fazer um backup, só lembre o seguinte, + +01:00.740 --> 01:01.100 +queira fazer um backup, só lembre o seguinte, + +01:01.100 --> 01:01.320 +queira fazer um backup, só lembre o seguinte, + +01:01.320 --> 01:01.460 +queira fazer um backup, lembre o seguinte, + +01:01.460 --> 01:01.660 +queira fazer um backup, só lembre o seguinte, + +01:01.660 --> 01:01.780 +queira fazer um backup, só lembre o seguinte, + +01:01.780 --> 01:02.000 +queira fazer um backup, só lembre o seguinte, + +01:02.160 --> 01:02.580 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:02.580 --> 01:02.680 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:02.680 --> 01:03.020 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:03.020 --> 01:03.500 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:03.500 --> 01:03.740 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:03.740 --> 01:04.680 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:04.680 --> 01:04.740 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:04.740 --> 01:05.200 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:05.200 --> 01:05.560 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:05.560 --> 01:05.680 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:05.680 --> 01:05.720 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:05.720 --> 01:05.820 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:05.820 --> 01:07.500 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:07.500 --> 01:07.640 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:07.640 --> 01:07.940 +conforme o HD vai sendo utilizado, a tendência é que ele comece a falhar + +01:07.940 --> 01:08.880 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:08.880 --> 01:09.340 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:09.340 --> 01:09.620 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:09.620 --> 01:09.900 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:09.900 --> 01:10.520 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:10.520 --> 01:10.720 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:10.720 --> 01:10.960 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:10.960 --> 01:11.340 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:11.340 --> 01:11.540 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:11.540 --> 01:11.880 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:11.880 --> 01:12.020 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:12.020 --> 01:12.240 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:12.240 --> 01:12.520 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:12.520 --> 01:12.840 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:12.840 --> 01:13.360 +e pode ser que não seja tão seguro assim guardar os dados nesse HD antigo, + +01:13.920 --> 01:14.160 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:14.160 --> 01:14.360 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:14.360 --> 01:14.820 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:14.820 --> 01:14.920 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:14.920 --> 01:15.000 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:15.000 --> 01:15.240 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:15.240 --> 01:15.380 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:15.380 --> 01:15.600 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:15.600 --> 01:17.040 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:17.040 --> 01:17.340 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:17.340 --> 01:17.660 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:17.660 --> 01:17.840 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:17.840 --> 01:18.040 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:18.040 --> 01:18.180 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:18.180 --> 01:18.480 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:18.480 --> 01:18.720 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:18.720 --> 01:19.140 +então não recomendo, por exemplo, ser o único lugar onde você vai guardar alguns arquivos. + +01:20.940 --> 01:21.420 +Essa case aqui eu comprei no Mercado Livre, + +01:21.420 --> 01:21.680 +Essa case aqui eu comprei no Mercado Livre, + +01:21.680 --> 01:21.840 +Essa case aqui eu comprei no Mercado Livre, + +01:21.840 --> 01:21.960 +Essa case aqui eu comprei no Mercado Livre, + +01:21.960 --> 01:22.240 +Essa case aqui eu comprei no Mercado Livre, + +01:22.240 --> 01:22.400 +Essa case aqui eu comprei no Mercado Livre, + +01:22.400 --> 01:22.720 +Essa case aqui eu comprei no Mercado Livre, + +01:22.720 --> 01:23.100 +Essa case aqui eu comprei no Mercado Livre, + +01:23.400 --> 01:23.460 +vou ver se eu deixo um link na descrição para compra, + +01:23.460 --> 01:23.540 +vou ver se eu deixo um link na descrição para compra, + +01:23.540 --> 01:23.600 +vou ver se eu deixo um link na descrição para compra, + +01:23.600 --> 01:23.700 +vou ver se eu deixo um link na descrição para compra, + +01:23.700 --> 01:23.840 +vou ver se eu deixo um link na descrição para compra, + +01:23.840 --> 01:23.940 +vou ver se eu deixo um link na descrição para compra, + +01:23.940 --> 01:24.080 +vou ver se eu deixo um link na descrição para compra, + +01:24.080 --> 01:24.220 +vou ver se eu deixo um link na descrição para compra, + +01:24.220 --> 01:24.660 +vou ver se eu deixo um link na descrição para compra, + +01:24.660 --> 01:26.100 +vou ver se eu deixo um link na descrição para compra, + +01:26.100 --> 01:26.500 +vou ver se eu deixo um link na descrição para compra, + +01:26.500 --> 01:27.040 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:27.040 --> 01:27.240 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:27.240 --> 01:27.340 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:27.340 --> 01:27.480 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:27.480 --> 01:27.640 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:27.640 --> 01:27.960 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:27.960 --> 01:28.240 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:28.240 --> 01:28.600 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:28.600 --> 01:29.380 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:29.380 --> 01:29.780 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:29.780 --> 01:30.120 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:30.120 --> 01:30.300 +vai custar em torno de uns 20 reais e ela vem com tudo o que você precisa, + +01:30.300 --> 01:30.520 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:30.520 --> 01:30.640 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:30.640 --> 01:30.740 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:30.740 --> 01:30.740 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:30.740 --> 01:30.940 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:30.940 --> 01:31.300 +vai custar em torno de uns 20 reais e ela vem já com tudo o que você precisa, + +01:31.380 --> 01:31.660 +inclusive com a chave, então tá escuta aqui. + +01:31.660 --> 01:31.900 +inclusive com a chave, então tá escuta aqui. + +01:31.900 --> 01:32.060 +inclusive com a chave, então tá escuta aqui. + +01:32.060 --> 01:32.380 +inclusive com a chave, então tá escuta aqui. + +01:32.380 --> 01:32.680 +inclusive com a chave, então tá escuta aqui. + +01:32.680 --> 01:32.880 +inclusive com a chave, então tá escuta aqui. + +01:32.880 --> 01:32.940 +inclusive com a chave, então escuta aqui. + +01:32.940 --> 01:33.240 +inclusive com a chave, então tá escuta aqui. + +01:33.240 --> 01:33.460 +inclusive com a chave, então tá escuta aqui. + +01:34.180 --> 01:34.660 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:34.660 --> 01:34.720 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:34.720 --> 01:35.160 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:35.160 --> 01:35.340 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:35.340 --> 01:35.460 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:35.460 --> 01:35.760 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:35.760 --> 01:35.960 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:35.960 --> 01:36.200 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:36.200 --> 01:36.460 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:36.460 --> 01:36.640 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:36.640 --> 01:36.860 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:36.860 --> 01:37.220 +Faço a instalação e tal, vamos lá, vou tirar aqui. + +01:39.760 --> 01:39.940 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:39.940 --> 01:40.300 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:40.300 --> 01:40.960 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:40.960 --> 01:41.480 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:41.480 --> 01:41.640 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:41.640 --> 01:41.900 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:41.900 --> 01:42.000 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:42.000 --> 01:42.080 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:42.080 --> 01:42.320 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:42.320 --> 01:42.480 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:42.480 --> 01:42.520 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:42.520 --> 01:42.700 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:42.700 --> 01:43.400 +Ela é relativamente pequena, simples, tem algumas que são mais bonitas, + +01:43.880 --> 01:43.960 +então aqui já vem a tampinha da case, + +01:43.960 --> 01:44.100 +então aqui já vem a tampinha da case, + +01:44.100 --> 01:44.260 +então aqui vem a tampinha da case, + +01:44.260 --> 01:44.420 +então aqui já vem a tampinha da case, + +01:44.420 --> 01:44.540 +então aqui já vem a tampinha da case, + +01:44.540 --> 01:44.920 +então aqui já vem a tampinha da case, + +01:44.920 --> 01:45.100 +então aqui já vem a tampinha da case, + +01:45.100 --> 01:45.440 +então aqui já vem a tampinha da case, + +01:46.900 --> 01:47.200 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:47.200 --> 01:47.560 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:47.560 --> 01:47.920 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:47.920 --> 01:48.280 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:48.280 --> 01:48.480 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:48.480 --> 01:48.900 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:48.900 --> 01:49.260 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:49.260 --> 01:49.660 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:49.660 --> 01:49.860 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:49.860 --> 01:49.980 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:49.980 --> 01:50.020 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:50.020 --> 01:50.160 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:50.160 --> 01:50.460 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:50.460 --> 01:50.660 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:50.660 --> 01:51.020 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:51.020 --> 01:51.180 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:51.180 --> 01:51.260 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:51.260 --> 01:51.440 +eu preferi comprar a mais barata porque nesse caso eu não queria gastar muito dinheiro com esse HD. + +01:51.860 --> 01:52.060 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:52.060 --> 01:52.240 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:52.240 --> 01:52.660 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:52.660 --> 01:53.160 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:53.160 --> 01:54.720 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:54.720 --> 01:55.020 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:55.020 --> 01:55.240 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:55.240 --> 01:55.360 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:55.360 --> 01:55.780 +Vem uma chavezinha Philips para ajudar aqui na instalação, + +01:55.780 --> 01:56.460 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:56.460 --> 01:56.640 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:56.640 --> 01:56.920 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:56.920 --> 01:58.380 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:58.380 --> 01:58.580 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:58.580 --> 01:58.820 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:58.820 --> 01:59.040 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:59.040 --> 01:59.280 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:59.280 --> 01:59.400 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:59.400 --> 01:59.660 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:59.660 --> 01:59.800 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +01:59.800 --> 02:00.060 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +02:00.060 --> 02:00.620 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +02:00.620 --> 02:01.060 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +02:01.060 --> 02:01.640 +vem o cabo, esse cabo aqui é um cabo que tem em uma ponta, + +02:02.640 --> 02:02.800 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:02.800 --> 02:03.160 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:03.160 --> 02:03.980 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:03.980 --> 02:04.380 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:04.380 --> 02:04.940 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:04.940 --> 02:05.120 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:05.120 --> 02:05.160 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:05.160 --> 02:05.280 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:05.280 --> 02:05.520 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:05.520 --> 02:05.920 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:05.920 --> 02:06.120 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:06.120 --> 02:06.320 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:06.320 --> 02:06.440 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:06.440 --> 02:06.720 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:06.720 --> 02:07.060 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:07.060 --> 02:07.220 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:07.220 --> 02:07.500 +ele tem um conector, que é esse daqui, que vai no HD, na case, + +02:08.000 --> 02:08.160 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:08.160 --> 02:08.300 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:08.300 --> 02:08.460 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:08.460 --> 02:08.760 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:08.760 --> 02:08.880 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:08.880 --> 02:08.980 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:08.980 --> 02:09.200 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:09.200 --> 02:09.600 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:09.600 --> 02:10.020 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:10.020 --> 02:10.700 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:10.700 --> 02:10.760 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:10.760 --> 02:10.860 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:10.860 --> 02:10.960 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:10.960 --> 02:11.060 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:11.060 --> 02:11.240 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:11.240 --> 02:11.360 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:11.360 --> 02:11.780 +e na outra ponta ele tem dois conectores USB que você vai ter que ligar no computador. + +02:12.360 --> 02:12.580 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:12.580 --> 02:13.020 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:13.020 --> 02:13.720 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:13.720 --> 02:13.940 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:13.940 --> 02:14.340 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:14.340 --> 02:14.520 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:14.520 --> 02:14.820 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:14.820 --> 02:14.980 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:14.980 --> 02:14.980 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:14.980 --> 02:15.080 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:15.080 --> 02:15.260 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:15.260 --> 02:15.760 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:15.760 --> 02:15.960 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:15.960 --> 02:16.240 +Um vai ser mais responsável pela energia e o outro pela transferência de dados. + +02:16.980 --> 02:17.200 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:17.200 --> 02:17.540 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:17.540 --> 02:17.920 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:17.920 --> 02:18.220 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:18.220 --> 02:19.380 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:19.380 --> 02:20.020 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:20.020 --> 02:20.640 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:20.640 --> 02:21.780 +Ainda aqui dentro tem um pacotinho de parafusos, + +02:21.780 --> 02:22.540 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:22.540 --> 02:22.780 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:22.780 --> 02:23.260 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:23.260 --> 02:23.380 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:23.380 --> 02:23.420 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:23.420 --> 02:23.500 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:23.500 --> 02:23.920 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:23.920 --> 02:24.040 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:24.040 --> 02:24.120 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:24.120 --> 02:24.280 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:24.280 --> 02:24.480 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:24.480 --> 02:25.000 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:25.000 --> 02:25.300 +tem dois parafusos aqui, não parece, mas tem dois parafusos aqui. + +02:26.840 --> 02:27.320 +Eu vou então começar, basicamente é muito simples o processo, + +02:27.320 --> 02:27.800 +Eu vou então começar, basicamente é muito simples o processo, + +02:27.800 --> 02:28.140 +Eu vou então começar, basicamente é muito simples o processo, + +02:28.140 --> 02:29.040 +Eu vou então começar, basicamente é muito simples o processo, + +02:29.040 --> 02:29.240 +Eu vou então começar, basicamente é muito simples o processo, + +02:29.240 --> 02:29.880 +Eu vou então começar, basicamente é muito simples o processo, + +02:29.880 --> 02:30.560 +Eu vou então começar, basicamente é muito simples o processo, + +02:30.560 --> 02:30.760 +Eu vou então começar, basicamente é muito simples o processo, + +02:30.760 --> 02:31.080 +Eu vou então começar, basicamente é muito simples o processo, + +02:31.080 --> 02:31.220 +Eu vou então começar, basicamente é muito simples o processo, + +02:31.220 --> 02:31.580 +Eu vou então começar, basicamente é muito simples o processo, + +02:32.200 --> 02:32.360 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:32.360 --> 02:32.920 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:32.920 --> 02:33.320 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:33.320 --> 02:33.980 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:33.980 --> 02:34.240 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:34.240 --> 02:34.680 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:34.680 --> 02:34.820 +você simplesmente vai conectar aqui, encaixa de um lado, + +02:34.820 --> 02:35.140 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:35.140 --> 02:35.280 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:35.280 --> 02:35.380 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:35.380 --> 02:35.620 +você simplesmente vai conectar aqui, só encaixa de um lado, + +02:35.820 --> 02:35.920 +então tem um conector menor e um maior, + +02:35.920 --> 02:36.140 +então tem um conector menor e um maior, + +02:36.140 --> 02:36.420 +então tem um conector menor e um maior, + +02:36.420 --> 02:36.720 +então tem um conector menor e um maior, + +02:36.720 --> 02:37.040 +então tem um conector menor e um maior, + +02:37.040 --> 02:37.200 +então tem um conector menor e um maior, + +02:37.200 --> 02:37.280 +então tem um conector menor e um maior, + +02:37.280 --> 02:37.520 +então tem um conector menor e um maior, + +02:38.220 --> 02:38.420 +olhando aqui para o HD tem um menor e um maior, + +02:38.420 --> 02:38.560 +olhando aqui para o HD tem um menor e um maior, + +02:38.560 --> 02:38.680 +olhando aqui para o HD tem um menor e um maior, + +02:38.680 --> 02:38.700 +olhando aqui para o HD tem um menor e um maior, + +02:38.700 --> 02:38.900 +olhando aqui para o HD tem um menor e um maior, + +02:38.900 --> 02:39.080 +olhando aqui para o HD tem um menor e um maior, + +02:39.080 --> 02:39.200 +olhando aqui para o HD tem um menor e um maior, + +02:39.200 --> 02:39.420 +olhando aqui para o HD tem um menor e um maior, + +02:39.420 --> 02:39.520 +olhando aqui para o HD tem um menor e um maior, + +02:39.520 --> 02:39.600 +olhando aqui para o HD tem um menor e um maior, + +02:39.600 --> 02:39.820 +olhando aqui para o HD tem um menor e um maior, + +02:40.280 --> 02:40.420 +então tem que virar aqui para encaixar direitinho, + +02:40.420 --> 02:40.620 +então tem que virar aqui para encaixar direitinho, + +02:40.620 --> 02:40.920 +então tem que virar aqui para encaixar direitinho, + +02:40.920 --> 02:41.340 +então tem que virar aqui para encaixar direitinho, + +02:41.340 --> 02:41.620 +então tem que virar aqui para encaixar direitinho, + +02:41.620 --> 02:42.420 +então tem que virar aqui para encaixar direitinho, + +02:42.420 --> 02:42.760 +então tem que virar aqui para encaixar direitinho, + +02:42.760 --> 02:43.200 +então tem que virar aqui para encaixar direitinho, + +02:43.960 --> 02:44.180 +daí eu encaixo e agora é só fechar aqui a case. + +02:44.180 --> 02:44.580 +daí eu encaixo e agora é só fechar aqui a case. + +02:44.580 --> 02:45.540 +daí eu encaixo e agora é só fechar aqui a case. + +02:45.540 --> 02:47.820 +daí eu encaixo e agora é só fechar aqui a case. + +02:47.820 --> 02:49.400 +daí eu encaixo e agora é só fechar aqui a case. + +02:49.400 --> 02:49.560 +daí eu encaixo e agora é só fechar aqui a case. + +02:49.560 --> 02:49.720 +daí eu encaixo e agora é fechar aqui a case. + +02:49.720 --> 02:50.320 +daí eu encaixo e agora é só fechar aqui a case. + +02:50.320 --> 02:50.520 +daí eu encaixo e agora é só fechar aqui a case. + +02:50.520 --> 02:50.660 +daí eu encaixo e agora é só fechar aqui a case. + +02:50.660 --> 02:50.880 +daí eu encaixo e agora é só fechar aqui a case. + +02:50.880 --> 02:51.840 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:51.840 --> 02:53.480 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:53.480 --> 02:53.660 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:53.660 --> 02:53.780 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:53.780 --> 02:53.940 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:53.940 --> 02:54.000 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:54.000 --> 02:54.360 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:54.360 --> 02:54.800 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:54.800 --> 02:55.080 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:55.080 --> 02:55.200 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:55.200 --> 02:55.320 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:55.320 --> 02:55.640 +Então tem aqui a case, obviamente outros cases vão ser diferentes, + +02:55.780 --> 02:55.840 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:55.840 --> 02:55.960 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:55.960 --> 02:56.320 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:56.320 --> 02:56.520 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:56.520 --> 02:56.640 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:56.640 --> 02:56.740 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:56.740 --> 02:56.860 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:56.860 --> 02:56.980 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:56.980 --> 02:57.260 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:57.260 --> 02:58.360 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:58.360 --> 02:58.500 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:58.500 --> 02:58.960 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:58.960 --> 02:59.120 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:59.120 --> 02:59.400 +mas é basicamente isso, você vai ter que conectar no circuito da case, + +02:59.940 --> 03:00.460 +colocar o HD dentro da case e fechar a case. + +03:00.460 --> 03:00.620 +colocar o HD dentro da case e fechar a case. + +03:00.620 --> 03:00.800 +colocar o HD dentro da case e fechar a case. + +03:00.800 --> 03:01.020 +colocar o HD dentro da case e fechar a case. + +03:01.020 --> 03:01.160 +colocar o HD dentro da case e fechar a case. + +03:01.160 --> 03:01.480 +colocar o HD dentro da case e fechar a case. + +03:01.480 --> 03:02.320 +colocar o HD dentro da case e fechar a case. + +03:02.320 --> 03:03.240 +colocar o HD dentro da case e fechar a case. + +03:03.240 --> 03:03.540 +colocar o HD dentro da case e fechar a case. + +03:03.540 --> 03:03.740 +colocar o HD dentro da case e fechar a case. + +03:04.060 --> 03:04.220 +Não tem muito mais segredo além disso não. + +03:04.220 --> 03:04.480 +Não tem muito mais segredo além disso não. + +03:04.480 --> 03:05.040 +Não tem muito mais segredo além disso não. + +03:05.040 --> 03:05.260 +Não tem muito mais segredo além disso não. + +03:05.260 --> 03:05.660 +Não tem muito mais segredo além disso não. + +03:05.660 --> 03:05.860 +Não tem muito mais segredo além disso não. + +03:05.860 --> 03:06.120 +Não tem muito mais segredo além disso não. + +03:06.120 --> 03:06.400 +Não tem muito mais segredo além disso não. + +03:07.220 --> 03:07.500 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:07.500 --> 03:07.740 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:07.740 --> 03:08.060 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:08.060 --> 03:08.260 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:08.260 --> 03:08.500 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:08.500 --> 03:08.840 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:08.840 --> 03:09.000 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:09.000 --> 03:09.380 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:09.380 --> 03:09.500 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:09.500 --> 03:09.740 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:09.740 --> 03:10.480 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:10.480 --> 03:10.920 +Essa daqui por ser mais barata eu percebi que em alguns modelos + +03:10.920 --> 03:11.120 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:11.120 --> 03:11.320 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:11.320 --> 03:11.620 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:11.620 --> 03:13.040 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:13.040 --> 03:13.300 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:13.300 --> 03:13.800 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:13.800 --> 03:17.140 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:17.140 --> 03:17.760 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:17.760 --> 03:17.960 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:17.960 --> 03:18.260 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:18.260 --> 03:18.400 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:18.400 --> 03:18.460 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:18.460 --> 03:18.780 +tem uma certa diferença aqui entre o tamanho da case e a tampa, + +03:18.780 --> 03:18.920 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:18.920 --> 03:19.080 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:19.080 --> 03:19.480 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:19.480 --> 03:19.660 +as vezes não para colocar, deixa eu ver se não tem nada dentro aqui. + +03:19.660 --> 03:19.820 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:19.820 --> 03:20.240 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:20.240 --> 03:20.440 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:20.440 --> 03:20.620 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:20.620 --> 03:20.700 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:20.700 --> 03:20.760 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:20.760 --> 03:20.860 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:20.860 --> 03:21.000 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:21.000 --> 03:21.100 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:21.100 --> 03:21.320 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:21.320 --> 03:21.660 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:21.660 --> 03:22.060 +as vezes não dá para colocar, deixa eu ver se não tem nada dentro aqui. + +03:23.180 --> 03:23.440 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:23.440 --> 03:23.640 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:23.640 --> 03:23.880 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:23.880 --> 03:24.040 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:24.040 --> 03:24.200 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:24.200 --> 03:24.440 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:24.440 --> 03:24.640 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:24.640 --> 03:24.740 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:24.740 --> 03:24.840 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:24.840 --> 03:24.940 +Não, não parece ter nada dentro não, é uma questão de encaixe mesmo, + +03:24.940 --> 03:25.060 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:25.060 --> 03:25.280 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:25.280 --> 03:25.440 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:25.440 --> 03:25.820 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:25.820 --> 03:26.020 +Não, não parece ter nada dentro não, é só uma questão de encaixe mesmo, + +03:26.120 --> 03:26.240 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:26.240 --> 03:26.500 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:26.500 --> 03:27.680 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:27.680 --> 03:28.040 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:28.040 --> 03:28.260 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:28.260 --> 03:28.360 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:28.360 --> 03:28.500 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:28.500 --> 03:28.700 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:28.700 --> 03:28.920 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:28.920 --> 03:29.080 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:29.080 --> 03:29.240 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:29.240 --> 03:29.460 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:29.460 --> 03:29.800 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:29.800 --> 03:29.980 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:29.980 --> 03:30.120 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:30.120 --> 03:30.260 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:30.260 --> 03:30.620 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:30.620 --> 03:30.720 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:30.720 --> 03:30.800 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:30.800 --> 03:31.120 +vamos ver se assim vai, ah foi, era o lado, então eu estava colocando o lado errado. + +03:31.840 --> 03:32.020 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:32.020 --> 03:32.280 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:32.280 --> 03:32.500 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:32.500 --> 03:32.700 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:32.700 --> 03:32.780 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:32.780 --> 03:32.840 +Bom, aqui agora é colocar um parafuso desse lado, + +03:32.840 --> 03:33.080 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:33.080 --> 03:33.200 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:33.200 --> 03:33.500 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:33.500 --> 03:33.680 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:33.680 --> 03:33.980 +Bom, aqui agora é só colocar um parafuso desse lado, + +03:34.500 --> 03:34.660 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:34.660 --> 03:34.960 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:34.960 --> 03:35.160 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:35.160 --> 03:35.340 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:35.340 --> 03:35.620 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:35.620 --> 03:36.240 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:36.240 --> 03:36.420 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:36.420 --> 03:36.600 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:36.600 --> 03:36.760 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:36.760 --> 03:36.920 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:36.920 --> 03:37.420 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:37.420 --> 03:37.680 +um parafuso desse outro lado, vamos tirar aqui os parafusos então, + +03:40.930 --> 03:41.330 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:41.330 --> 03:41.730 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:41.730 --> 03:41.810 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:41.810 --> 03:42.410 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:42.410 --> 03:43.130 +e depois de aparafusar vai estar pronta, é só ligar no computador. + +03:43.130 --> 03:43.270 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:43.270 --> 03:43.390 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:43.390 --> 03:43.690 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:43.690 --> 03:43.810 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:43.810 --> 03:43.810 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:43.810 --> 03:43.890 +e depois de aparafusar já vai estar pronta, é ligar no computador. + +03:43.890 --> 03:44.130 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:44.130 --> 03:44.250 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:44.250 --> 03:44.710 +e depois de aparafusar já vai estar pronta, é só ligar no computador. + +03:47.110 --> 03:47.590 +Daí qualquer sistema operacional que você esteja utilizando, + +03:47.590 --> 03:48.070 +Daí qualquer sistema operacional que você esteja utilizando, + +03:48.070 --> 03:48.310 +Daí qualquer sistema operacional que você esteja utilizando, + +03:48.310 --> 03:48.650 +Daí qualquer sistema operacional que você esteja utilizando, + +03:48.650 --> 03:48.790 +Daí qualquer sistema operacional que você esteja utilizando, + +03:48.790 --> 03:48.870 +Daí qualquer sistema operacional que você esteja utilizando, + +03:48.870 --> 03:49.050 +Daí qualquer sistema operacional que você esteja utilizando, + +03:49.050 --> 03:49.510 +Daí qualquer sistema operacional que você esteja utilizando, + +03:49.610 --> 03:49.790 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:49.790 --> 03:50.050 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:50.050 --> 03:50.350 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:50.350 --> 03:50.550 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:50.550 --> 03:51.210 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:51.210 --> 03:52.230 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:52.230 --> 03:52.450 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:52.450 --> 03:52.670 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:52.670 --> 03:53.030 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:53.030 --> 03:53.210 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:53.210 --> 03:53.330 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:53.330 --> 03:53.430 +Windows, Linux, Gnolinux, Mac OS X, por vai, + +03:53.430 --> 03:53.590 +Windows, Linux, Gnolinux, Mac OS X, por aí vai, + +03:54.250 --> 03:54.490 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:54.490 --> 03:54.690 +você vai conseguir abrir como se fosse um pendrive mesmo. + +03:54.690 --> 03:54.830 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:54.830 --> 03:55.350 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:55.350 --> 03:57.250 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:57.250 --> 03:57.410 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:57.410 --> 03:57.530 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:57.530 --> 03:57.630 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:57.630 --> 03:57.770 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:57.770 --> 03:58.150 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:58.150 --> 03:58.410 +você já vai conseguir abrir como se fosse um pendrive mesmo. + +03:58.890 --> 03:59.010 + toma cuidado que caso tenha algum problema na partição do seu HD, + +03:59.010 --> 03:59.190 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +03:59.190 --> 03:59.670 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +03:59.670 --> 04:00.210 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:00.210 --> 04:00.710 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:00.710 --> 04:01.010 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:01.010 --> 04:01.270 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:01.270 --> 04:01.610 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:01.610 --> 04:01.830 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:01.830 --> 04:02.330 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:02.330 --> 04:03.150 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:03.150 --> 04:03.810 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:03.810 --> 04:04.150 +Só toma cuidado que caso tenha algum problema na partição do seu HD, + +04:04.950 --> 04:05.170 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:05.170 --> 04:05.390 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:05.390 --> 04:05.530 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:05.530 --> 04:05.650 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:05.650 --> 04:05.930 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:05.930 --> 04:06.250 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:06.250 --> 04:06.450 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:06.450 --> 04:06.630 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:06.630 --> 04:06.770 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:06.770 --> 04:06.950 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:06.950 --> 04:07.130 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:07.130 --> 04:07.230 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:07.230 --> 04:07.870 +porque, sei lá, se por exemplo o sistema não está iniciando + +04:08.490 --> 04:08.970 +e aí por isso você quer recuperar os arquivos, + +04:08.970 --> 04:09.110 +e por isso você quer recuperar os arquivos, + +04:09.110 --> 04:09.290 +e aí por isso você quer recuperar os arquivos, + +04:09.290 --> 04:09.530 +e aí por isso você quer recuperar os arquivos, + +04:09.530 --> 04:09.910 +e aí por isso você quer recuperar os arquivos, + +04:09.910 --> 04:10.050 +e aí por isso você quer recuperar os arquivos, + +04:10.050 --> 04:10.390 +e aí por isso você quer recuperar os arquivos, + +04:10.390 --> 04:10.490 +e aí por isso você quer recuperar os arquivos, + +04:10.490 --> 04:10.890 +e aí por isso você quer recuperar os arquivos, + +04:10.890 --> 04:11.670 +cuidado que no Windows ele costuma sugerir formatar, + +04:11.670 --> 04:11.910 +cuidado que no Windows ele costuma sugerir formatar, + +04:11.910 --> 04:12.050 +cuidado que no Windows ele costuma sugerir formatar, + +04:12.050 --> 04:12.430 +cuidado que no Windows ele costuma sugerir formatar, + +04:12.430 --> 04:12.630 +cuidado que no Windows ele costuma sugerir formatar, + +04:12.630 --> 04:12.910 +cuidado que no Windows ele costuma sugerir formatar, + +04:12.910 --> 04:13.430 +cuidado que no Windows ele costuma sugerir formatar, + +04:13.430 --> 04:14.330 +cuidado que no Windows ele costuma sugerir formatar, + +04:14.810 --> 04:15.030 +dado que ele não consegue as vezes identificar a partição. + +04:15.030 --> 04:15.210 +dado que ele não consegue as vezes identificar a partição. + +04:15.210 --> 04:15.370 +dado que ele não consegue as vezes identificar a partição. + +04:15.370 --> 04:15.550 +dado que ele não consegue as vezes identificar a partição. + +04:15.550 --> 04:16.030 +dado que ele não consegue as vezes identificar a partição. + +04:16.030 --> 04:16.190 +dado que ele não consegue as vezes identificar a partição. + +04:16.190 --> 04:16.350 +dado que ele não consegue as vezes identificar a partição. + +04:16.350 --> 04:16.910 +dado que ele não consegue as vezes identificar a partição. + +04:16.910 --> 04:17.070 +dado que ele não consegue as vezes identificar a partição. + +04:17.070 --> 04:17.390 +dado que ele não consegue as vezes identificar a partição. + +04:18.030 --> 04:18.210 +Não faça isso, se você formatar você vai perder os seus dados, + +04:18.210 --> 04:18.530 +Não faça isso, se você formatar você vai perder os seus dados, + +04:18.530 --> 04:18.810 +Não faça isso, se você formatar você vai perder os seus dados, + +04:18.810 --> 04:18.910 +Não faça isso, se você formatar você vai perder os seus dados, + +04:18.910 --> 04:19.050 +Não faça isso, se você formatar você vai perder os seus dados, + +04:19.050 --> 04:19.230 +Não faça isso, se você formatar você vai perder os seus dados, + +04:19.230 --> 04:19.630 +Não faça isso, se você formatar você vai perder os seus dados, + +04:19.630 --> 04:19.710 +Não faça isso, se você formatar você vai perder os seus dados, + +04:19.710 --> 04:19.830 +Não faça isso, se você formatar você vai perder os seus dados, + +04:19.830 --> 04:20.090 +Não faça isso, se você formatar você vai perder os seus dados, + +04:20.090 --> 04:20.250 +Não faça isso, se você formatar você vai perder os seus dados, + +04:20.250 --> 04:20.390 +Não faça isso, se você formatar você vai perder os seus dados, + +04:20.390 --> 04:20.670 +Não faça isso, se você formatar você vai perder os seus dados, + +04:21.530 --> 04:21.530 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:21.530 --> 04:22.710 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:22.710 --> 04:22.850 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:22.850 --> 04:22.950 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:22.950 --> 04:23.030 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:23.030 --> 04:23.170 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:23.170 --> 04:23.630 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:23.630 --> 04:25.010 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:25.010 --> 04:25.510 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:25.510 --> 04:26.550 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:26.550 --> 04:27.110 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:27.110 --> 04:27.230 +e enfim, você não vai conseguir depois tão facilmente recuperá-los. + +04:27.690 --> 04:27.870 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:27.870 --> 04:28.270 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:28.270 --> 04:28.710 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:28.710 --> 04:29.070 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:29.070 --> 04:29.470 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:29.470 --> 04:29.690 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:29.690 --> 04:30.030 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:30.030 --> 04:30.650 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:30.650 --> 04:31.030 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:31.030 --> 04:31.190 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:31.190 --> 04:31.310 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:31.310 --> 04:31.890 +Nesse caso eu sugiro utilizar algum sistema mais robusto como o Gnolinux. + +04:32.470 --> 04:32.710 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:32.710 --> 04:32.810 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:32.810 --> 04:32.930 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:32.930 --> 04:33.330 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:33.330 --> 04:34.050 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:34.050 --> 04:34.290 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:34.290 --> 04:34.530 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:34.530 --> 04:34.770 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:34.770 --> 04:35.010 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:35.010 --> 04:35.230 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:35.230 --> 04:35.490 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:35.490 --> 04:35.730 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:35.730 --> 04:35.970 +Então aqui está pronto, basta então agora pegar esse lado do cabo + +04:36.450 --> 04:36.550 +que só tem um conector, conectar aqui, + +04:36.550 --> 04:36.670 +que tem um conector, conectar aqui, + +04:36.670 --> 04:36.830 +que só tem um conector, conectar aqui, + +04:36.830 --> 04:36.970 +que só tem um conector, conectar aqui, + +04:36.970 --> 04:37.370 +que só tem um conector, conectar aqui, + +04:37.370 --> 04:38.130 +que só tem um conector, conectar aqui, + +04:38.130 --> 04:38.650 +que só tem um conector, conectar aqui, + +04:38.650 --> 04:38.890 +que só tem um conector, conectar aqui, + +04:38.890 --> 04:39.470 +e aí vai ser só ligar no computador. + +04:39.470 --> 04:41.010 +e vai ser só ligar no computador. + +04:41.010 --> 04:41.530 +e aí vai ser só ligar no computador. + +04:41.530 --> 04:41.670 +e aí vai ser só ligar no computador. + +04:41.670 --> 04:41.930 +e aí vai ser ligar no computador. + +04:41.930 --> 04:42.750 +e aí vai ser só ligar no computador. + +04:42.750 --> 04:42.870 +e aí vai ser só ligar no computador. + +04:42.870 --> 04:43.290 +e aí vai ser só ligar no computador. + +04:44.430 --> 04:44.550 +Bom, estou aqui no computador agora, + +04:44.550 --> 04:44.610 +Bom, estou aqui no computador agora, + +04:44.610 --> 04:44.690 +Bom, estou aqui no computador agora, + +04:44.690 --> 04:44.830 +Bom, estou aqui no computador agora, + +04:44.830 --> 04:44.930 +Bom, estou aqui no computador agora, + +04:44.930 --> 04:45.310 +Bom, estou aqui no computador agora, + +04:45.310 --> 04:45.690 +Bom, estou aqui no computador agora, + +04:46.650 --> 04:47.130 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:47.130 --> 04:47.230 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:47.230 --> 04:47.430 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:47.430 --> 04:47.950 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:47.950 --> 04:48.110 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:48.110 --> 04:48.210 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:48.210 --> 04:48.390 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:48.390 --> 04:48.530 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:48.530 --> 04:48.770 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:48.770 --> 04:49.530 +para quem tiver curiosidade eu estou usando o Debian Gnolinux + +04:49.530 --> 04:49.930 +e o meu gerenciador de janelas é o i3 da Bram, + +04:49.930 --> 04:50.050 +e o meu gerenciador de janelas é o i3 da Bram, + +04:50.050 --> 04:50.190 +e o meu gerenciador de janelas é o i3 da Bram, + +04:50.190 --> 04:50.650 +e o meu gerenciador de janelas é o i3 da Bram, + +04:50.650 --> 04:50.890 +e o meu gerenciador de janelas é o i3 da Bram, + +04:50.890 --> 04:51.290 +e o meu gerenciador de janelas é o i3 da Bram, + +04:51.290 --> 04:51.470 +e o meu gerenciador de janelas é o i3 da Bram, + +04:51.470 --> 04:51.610 +e o meu gerenciador de janelas é o i3 da Bram, + +04:51.610 --> 04:52.170 +e o meu gerenciador de janelas é o i3 da Bram, + +04:52.170 --> 04:52.330 +e o meu gerenciador de janelas é o i3 da Bram, + +04:52.330 --> 04:52.610 +e o meu gerenciador de janelas é o i3 da Bram, + +04:52.730 --> 04:53.410 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:53.410 --> 04:53.490 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:53.490 --> 04:53.830 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:53.830 --> 04:53.970 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:53.970 --> 04:54.350 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:54.350 --> 04:54.590 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:54.590 --> 04:54.730 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:54.730 --> 04:55.070 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:55.070 --> 04:55.790 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:55.790 --> 04:55.970 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:55.970 --> 04:56.050 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:56.050 --> 04:56.130 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:56.130 --> 04:56.250 +que eu recomendo pra caralho, uso desde 2012 se não me engano. + +04:56.770 --> 04:56.950 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:56.950 --> 04:57.070 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:57.070 --> 04:57.210 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:57.210 --> 04:57.310 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:57.310 --> 04:57.450 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:57.450 --> 04:57.730 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:57.730 --> 04:57.890 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:57.890 --> 04:57.990 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:57.990 --> 04:58.190 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:58.190 --> 04:58.630 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:58.630 --> 04:59.550 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +04:59.550 --> 05:00.090 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +05:00.090 --> 05:00.590 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +05:00.590 --> 05:01.110 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +05:01.110 --> 05:01.250 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +05:01.250 --> 05:01.350 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +05:01.350 --> 05:01.630 +Vamos lá, eu vou plugar aqui os dois cabos USB do HD externo, do case, + +05:03.610 --> 05:04.090 +e deve aparecer aqui do ladinho o novo volume, + +05:04.090 --> 05:04.570 +e deve aparecer aqui do ladinho o novo volume, + +05:04.570 --> 05:04.990 +e deve aparecer aqui do ladinho o novo volume, + +05:04.990 --> 05:05.210 +e deve aparecer aqui do ladinho o novo volume, + +05:05.210 --> 05:05.330 +e deve aparecer aqui do ladinho o novo volume, + +05:05.330 --> 05:05.730 +e deve aparecer aqui do ladinho o novo volume, + +05:05.730 --> 05:06.490 +e deve aparecer aqui do ladinho o novo volume, + +05:06.490 --> 05:06.910 +e deve aparecer aqui do ladinho o novo volume, + +05:06.910 --> 05:07.390 +e deve aparecer aqui do ladinho o novo volume, + +05:07.390 --> 05:07.830 +que seria o correspondente ao HD. + +05:07.830 --> 05:08.210 +que seria o correspondente ao HD. + +05:08.210 --> 05:08.590 +que seria o correspondente ao HD. + +05:08.590 --> 05:09.170 +que seria o correspondente ao HD. + +05:09.170 --> 05:09.290 +que seria o correspondente ao HD. + +05:09.290 --> 05:09.610 +que seria o correspondente ao HD. + +05:10.410 --> 05:10.850 +Está lá, é um volume de 500 gigabytes, + +05:10.850 --> 05:11.050 +Está lá, é um volume de 500 gigabytes, + +05:11.050 --> 05:11.470 +Está lá, é um volume de 500 gigabytes, + +05:11.470 --> 05:11.710 +Está lá, é um volume de 500 gigabytes, + +05:11.710 --> 05:11.830 +Está lá, é um volume de 500 gigabytes, + +05:11.830 --> 05:12.050 +Está lá, é um volume de 500 gigabytes, + +05:12.050 --> 05:12.210 +Está lá, é um volume de 500 gigabytes, + +05:12.210 --> 05:12.450 +Está lá, é um volume de 500 gigabytes, + +05:12.450 --> 05:12.970 +Está lá, é um volume de 500 gigabytes, + +05:13.830 --> 05:14.270 +que é o HD que eu tirei do notebook. + +05:14.270 --> 05:14.710 +que é o HD que eu tirei do notebook. + +05:14.710 --> 05:14.830 +que é o HD que eu tirei do notebook. + +05:14.830 --> 05:15.070 +que é o HD que eu tirei do notebook. + +05:15.070 --> 05:15.230 +que é o HD que eu tirei do notebook. + +05:15.230 --> 05:15.350 +que é o HD que eu tirei do notebook. + +05:15.350 --> 05:15.610 +que é o HD que eu tirei do notebook. + +05:15.610 --> 05:15.770 +que é o HD que eu tirei do notebook. + +05:15.770 --> 05:16.210 +que é o HD que eu tirei do notebook. + +05:16.750 --> 05:16.850 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:16.850 --> 05:16.970 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:16.970 --> 05:17.130 +Ah, um adendo, no vídeo, na parte anterior do vídeo, + +05:17.130 --> 05:17.250 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:17.250 --> 05:17.510 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:17.510 --> 05:17.730 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:17.730 --> 05:17.890 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:17.890 --> 05:18.250 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:18.250 --> 05:18.510 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:18.510 --> 05:18.650 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:18.650 --> 05:18.870 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:18.870 --> 05:19.170 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:19.170 --> 05:19.330 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:19.330 --> 05:19.550 +Ah, só um adendo, no vídeo, na parte anterior do vídeo, + +05:19.670 --> 05:19.750 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:19.750 --> 05:19.990 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:19.990 --> 05:20.450 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:20.450 --> 05:21.850 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:21.850 --> 05:22.230 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:22.230 --> 05:22.370 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:22.370 --> 05:22.670 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:22.670 --> 05:22.970 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:22.970 --> 05:23.450 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:23.450 --> 05:23.670 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:23.670 --> 05:23.870 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:23.870 --> 05:24.190 +eu falei sobre utilizar também um HD de computador, de desktop. + +05:24.830 --> 05:25.090 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:25.090 --> 05:25.470 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:25.470 --> 05:25.830 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:25.830 --> 05:26.050 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:26.050 --> 05:26.190 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:26.190 --> 05:26.310 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:26.310 --> 05:26.650 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:26.650 --> 05:26.870 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:26.870 --> 05:27.110 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:27.110 --> 05:27.310 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:27.310 --> 05:27.370 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:27.370 --> 05:27.470 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:27.470 --> 05:27.790 +Você pode sim, mas é obviamente a case tem que ser diferente, + +05:27.910 --> 05:28.030 +essa case que eu comprei é feita para HD de notebook, + +05:28.030 --> 05:28.310 +essa case que eu comprei é feita para HD de notebook, + +05:28.310 --> 05:28.430 +essa case que eu comprei é feita para HD de notebook, + +05:28.430 --> 05:28.510 +essa case que eu comprei é feita para HD de notebook, + +05:28.510 --> 05:28.750 +essa case que eu comprei é feita para HD de notebook, + +05:28.750 --> 05:28.870 +essa case que eu comprei é feita para HD de notebook, + +05:28.870 --> 05:29.070 +essa case que eu comprei é feita para HD de notebook, + +05:29.070 --> 05:29.270 +essa case que eu comprei é feita para HD de notebook, + +05:29.270 --> 05:30.070 +essa case que eu comprei é feita para HD de notebook, + +05:30.070 --> 05:30.510 +essa case que eu comprei é feita para HD de notebook, + +05:30.510 --> 05:31.030 +essa case que eu comprei é feita para HD de notebook, + +05:31.190 --> 05:31.250 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:31.250 --> 05:31.350 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:31.350 --> 05:31.550 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:31.550 --> 05:31.710 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:31.710 --> 05:31.890 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:31.890 --> 05:32.070 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:32.070 --> 05:32.630 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:32.630 --> 05:33.110 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:33.110 --> 05:33.250 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:33.250 --> 05:33.530 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:33.530 --> 05:33.730 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:33.730 --> 05:34.210 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:34.210 --> 05:34.390 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:34.390 --> 05:34.710 +é um HD de 2,5 polegadas, o HD de computador de desktop + +05:34.710 --> 05:35.250 +é um HD de 3,5 polegadas. + +05:35.250 --> 05:35.390 +é um HD de 3,5 polegadas. + +05:35.390 --> 05:35.590 +é um HD de 3,5 polegadas. + +05:35.590 --> 05:35.770 +é um HD de 3,5 polegadas. + +05:35.770 --> 05:35.990 +é um HD de 3,5 polegadas. + +05:35.990 --> 05:36.190 +é um HD de 3,5 polegadas. + +05:36.190 --> 05:36.670 +é um HD de 3,5 polegadas. + +05:36.670 --> 05:37.410 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:37.410 --> 05:37.590 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:37.590 --> 05:37.810 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:37.810 --> 05:37.810 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:37.810 --> 05:38.050 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:38.050 --> 05:38.310 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:38.310 --> 05:38.730 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:38.730 --> 05:39.290 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:39.290 --> 05:39.430 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:39.430 --> 05:39.790 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:39.790 --> 05:39.930 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:39.930 --> 05:40.250 +Então aqui a gente tem os arquivos de uma instalação do Ubuntu, + +05:40.330 --> 05:40.390 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:40.390 --> 05:40.410 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:40.410 --> 05:40.470 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:40.470 --> 05:40.590 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:40.590 --> 05:40.710 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:40.710 --> 05:40.790 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:40.790 --> 05:40.850 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:40.850 --> 05:40.970 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:40.970 --> 05:41.250 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:41.250 --> 05:42.210 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:42.210 --> 05:42.710 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:42.710 --> 05:43.270 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:43.270 --> 05:43.690 +se eu não me engano, que eu fiz no notebook mais antigo, + +05:43.890 --> 05:44.310 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:44.310 --> 05:44.430 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:44.430 --> 05:44.550 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:44.550 --> 05:44.750 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:44.750 --> 05:45.190 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:45.190 --> 05:45.230 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:45.230 --> 05:45.310 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:45.310 --> 05:45.510 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:45.510 --> 05:45.570 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:45.570 --> 05:45.710 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:45.710 --> 05:46.310 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:46.310 --> 05:47.150 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:47.150 --> 05:47.690 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:47.690 --> 05:47.890 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:47.890 --> 05:48.190 +e se eu quiser acessar, por exemplo, os arquivos pessoais dentro de home, + +05:48.450 --> 05:48.730 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:48.730 --> 05:48.890 +tem Álvaro, que é o meu usuário naquele computador, + +05:48.890 --> 05:49.370 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:49.370 --> 05:49.530 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:49.530 --> 05:49.610 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:49.610 --> 05:49.930 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:49.930 --> 05:50.090 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:50.090 --> 05:50.230 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:50.230 --> 05:50.630 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:50.630 --> 05:51.050 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:51.050 --> 05:51.450 +tem lá Álvaro, que é o meu usuário naquele computador, + +05:51.870 --> 05:51.950 +e aqui tem todos os downloads, documentos e tudo mais. + +05:51.950 --> 05:52.090 +e aqui tem todos os downloads, documentos e tudo mais. + +05:52.090 --> 05:52.270 +e aqui tem todos os downloads, documentos e tudo mais. + +05:52.270 --> 05:52.510 +e aqui tem todos os downloads, documentos e tudo mais. + +05:52.510 --> 05:53.070 +e aqui tem todos os downloads, documentos e tudo mais. + +05:53.070 --> 05:53.430 +e aqui tem todos os downloads, documentos e tudo mais. + +05:53.430 --> 05:53.610 +e aqui tem todos os downloads, documentos e tudo mais. + +05:53.610 --> 05:54.110 +e aqui tem todos os downloads, documentos e tudo mais. + +05:54.110 --> 05:54.250 +e aqui tem todos os downloads, documentos e tudo mais. + +05:54.250 --> 05:54.370 +e aqui tem todos os downloads, documentos e tudo mais. + +05:54.370 --> 05:54.590 +e aqui tem todos os downloads, documentos e tudo mais. + +05:55.170 --> 05:55.470 +Se esse HD fosse de um sistema Windows, + +05:55.470 --> 05:55.730 +Se esse HD fosse de um sistema Windows, + +05:55.730 --> 05:55.950 +Se esse HD fosse de um sistema Windows, + +05:55.950 --> 05:56.410 +Se esse HD fosse de um sistema Windows, + +05:56.410 --> 05:56.870 +Se esse HD fosse de um sistema Windows, + +05:56.870 --> 05:57.030 +Se esse HD fosse de um sistema Windows, + +05:57.030 --> 05:57.490 +Se esse HD fosse de um sistema Windows, + +05:57.490 --> 05:58.390 +Se esse HD fosse de um sistema Windows, + +05:59.250 --> 05:59.730 +provavelmente você entraria aqui na pasta usuários, + +05:59.730 --> 06:00.050 +provavelmente você entraria aqui na pasta usuários, + +06:00.050 --> 06:00.850 +provavelmente você entraria aqui na pasta usuários, + +06:00.850 --> 06:01.030 +provavelmente você entraria aqui na pasta usuários, + +06:01.030 --> 06:01.170 +provavelmente você entraria aqui na pasta usuários, + +06:01.170 --> 06:01.410 +provavelmente você entraria aqui na pasta usuários, + +06:01.410 --> 06:02.010 +provavelmente você entraria aqui na pasta usuários, + +06:02.630 --> 06:03.150 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:03.150 --> 06:03.330 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:03.330 --> 06:03.670 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:03.670 --> 06:04.150 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:04.150 --> 06:04.410 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:04.410 --> 06:04.610 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:04.610 --> 06:04.790 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:04.790 --> 06:05.310 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:05.310 --> 06:05.570 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:05.570 --> 06:05.790 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:05.790 --> 06:05.950 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:05.950 --> 06:06.230 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:06.230 --> 06:06.870 +e se fosse de um Mac OS X, ficaria na pasta users. + +06:07.770 --> 06:08.030 +Mas basicamente é esse o processo. + +06:08.030 --> 06:08.530 +Mas basicamente é esse o processo. + +06:08.530 --> 06:08.950 +Mas basicamente é esse o processo. + +06:08.950 --> 06:09.090 +Mas basicamente é esse o processo. + +06:09.090 --> 06:09.190 +Mas basicamente é esse o processo. + +06:09.190 --> 06:09.570 +Mas basicamente é esse o processo. + +06:10.050 --> 06:10.390 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:10.390 --> 06:10.490 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:10.490 --> 06:10.490 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:10.490 --> 06:10.550 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:10.550 --> 06:10.630 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:10.630 --> 06:10.870 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:10.870 --> 06:11.070 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:11.070 --> 06:11.150 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:11.150 --> 06:11.290 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:11.290 --> 06:11.650 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:11.650 --> 06:11.970 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:11.970 --> 06:12.190 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:12.190 --> 06:12.350 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:12.350 --> 06:12.810 +Agora o que eu vou fazer aqui, depois de salvar os meus arquivos, + +06:13.030 --> 06:13.330 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:13.330 --> 06:13.550 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:13.550 --> 06:13.730 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:13.730 --> 06:14.330 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:14.330 --> 06:14.730 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:14.730 --> 06:14.910 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:14.910 --> 06:15.150 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:15.150 --> 06:15.510 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:15.510 --> 06:15.750 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:15.750 --> 06:16.070 +é fazer uma formatação um pouco mais lenta nesse HD, + +06:16.650 --> 06:17.130 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:17.130 --> 06:17.590 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:17.590 --> 06:17.770 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:17.770 --> 06:18.270 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:18.270 --> 06:18.490 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:18.490 --> 06:18.810 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:18.810 --> 06:19.090 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:19.090 --> 06:19.250 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:19.250 --> 06:19.390 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:19.390 --> 06:19.390 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:19.390 --> 06:19.650 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:19.650 --> 06:19.770 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:19.770 --> 06:19.850 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:19.850 --> 06:20.130 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:20.130 --> 06:20.510 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:20.510 --> 06:20.750 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:20.750 --> 06:21.110 +uma formatação que impeça que, se eu, por exemplo, no futuro perder essa case, + +06:22.170 --> 06:22.690 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:22.690 --> 06:22.790 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:22.790 --> 06:22.990 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:22.990 --> 06:23.290 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:23.290 --> 06:23.850 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:23.850 --> 06:24.430 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:24.430 --> 06:24.630 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:24.630 --> 06:24.990 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:24.990 --> 06:25.510 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:25.510 --> 06:25.690 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:25.690 --> 06:25.930 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:25.930 --> 06:26.170 +impeça que alguma pessoa consiga recuperar esses dados antigos que estão aqui, + +06:26.170 --> 06:26.990 +e depois disso eu vou então poder usar, tranquilamente, + +06:26.990 --> 06:27.610 +e depois disso eu vou então poder usar, tranquilamente, + +06:27.610 --> 06:28.290 +e depois disso eu vou então poder usar, tranquilamente, + +06:28.290 --> 06:28.770 +e depois disso eu vou então poder usar, tranquilamente, + +06:28.770 --> 06:29.010 +e depois disso eu vou então poder usar, tranquilamente, + +06:29.010 --> 06:29.370 +e depois disso eu vou então poder usar, tranquilamente, + +06:29.370 --> 06:30.330 +e depois disso eu vou então poder usar, tranquilamente, + +06:30.330 --> 06:30.730 +e depois disso eu vou então poder usar, tranquilamente, + +06:30.730 --> 06:30.950 +e depois disso eu vou então poder usar, tranquilamente, + +06:30.950 --> 06:31.250 +e depois disso eu vou então poder usar, tranquilamente, + +06:32.090 --> 06:32.410 +esse HD externo para copiar dados e tudo mais. + +06:32.410 --> 06:32.810 +esse HD externo para copiar dados e tudo mais. + +06:32.810 --> 06:33.190 +esse HD externo para copiar dados e tudo mais. + +06:33.190 --> 06:33.350 +esse HD externo para copiar dados e tudo mais. + +06:33.350 --> 06:33.650 +esse HD externo para copiar dados e tudo mais. + +06:33.650 --> 06:34.110 +esse HD externo para copiar dados e tudo mais. + +06:34.110 --> 06:34.450 +esse HD externo para copiar dados e tudo mais. + +06:34.450 --> 06:34.590 +esse HD externo para copiar dados e tudo mais. + +06:34.590 --> 06:34.810 +esse HD externo para copiar dados e tudo mais. + +06:35.350 --> 06:35.550 +Então a ideia é essa. + +06:35.550 --> 06:35.850 +Então a ideia é essa. + +06:35.850 --> 06:36.010 +Então a ideia é essa. + +06:36.010 --> 06:36.210 +Então a ideia é essa. + +06:36.210 --> 06:36.390 +Então a ideia é essa. + +06:36.490 --> 06:36.650 +Eu vou fazer essa formatação para que seja irrecuperável + +06:36.650 --> 06:36.910 +Eu vou fazer essa formatação para que seja irrecuperável + +06:36.910 --> 06:37.310 +Eu vou fazer essa formatação para que seja irrecuperável + +06:37.310 --> 06:37.510 +Eu vou fazer essa formatação para que seja irrecuperável + +06:37.510 --> 06:38.050 +Eu vou fazer essa formatação para que seja irrecuperável + +06:38.050 --> 06:38.210 +Eu vou fazer essa formatação para que seja irrecuperável + +06:38.210 --> 06:38.890 +Eu vou fazer essa formatação para que seja irrecuperável + +06:38.890 --> 06:39.550 +Eu vou fazer essa formatação para que seja irrecuperável + +06:39.550 --> 06:40.670 +Eu vou fazer essa formatação para que seja irrecuperável + +06:40.670 --> 06:41.410 +o que está aqui em termos de passado dos dados, + +06:41.410 --> 06:41.570 +o que está aqui em termos de passado dos dados, + +06:41.570 --> 06:41.730 +o que está aqui em termos de passado dos dados, + +06:41.730 --> 06:42.010 +o que está aqui em termos de passado dos dados, + +06:42.010 --> 06:42.290 +o que está aqui em termos de passado dos dados, + +06:42.290 --> 06:42.490 +o que está aqui em termos de passado dos dados, + +06:42.490 --> 06:42.610 +o que está aqui em termos de passado dos dados, + +06:42.610 --> 06:43.030 +o que está aqui em termos de passado dos dados, + +06:43.030 --> 06:43.290 +o que está aqui em termos de passado dos dados, + +06:43.290 --> 06:43.650 +o que está aqui em termos de passado dos dados, + +06:44.270 --> 06:44.350 +e depois eu vou utilizar normalmente o HD. + +06:44.350 --> 06:44.630 +e depois eu vou utilizar normalmente o HD. + +06:44.630 --> 06:44.930 +e depois eu vou utilizar normalmente o HD. + +06:44.930 --> 06:45.270 +e depois eu vou utilizar normalmente o HD. + +06:45.270 --> 06:45.990 +e depois eu vou utilizar normalmente o HD. + +06:45.990 --> 06:46.570 +e depois eu vou utilizar normalmente o HD. + +06:46.570 --> 06:47.270 +e depois eu vou utilizar normalmente o HD. + +06:47.270 --> 06:47.510 +e depois eu vou utilizar normalmente o HD. + +06:47.910 --> 06:48.090 +Eu vou também, na verdade, antes de formatar, + +06:48.090 --> 06:48.230 +Eu vou também, na verdade, antes de formatar, + +06:48.230 --> 06:48.530 +Eu vou também, na verdade, antes de formatar, + +06:48.530 --> 06:48.810 +Eu vou também, na verdade, antes de formatar, + +06:48.810 --> 06:48.930 +Eu vou também, na verdade, antes de formatar, + +06:48.930 --> 06:49.150 +Eu vou também, na verdade, antes de formatar, + +06:49.150 --> 06:49.270 +Eu vou também, na verdade, antes de formatar, + +06:49.270 --> 06:49.430 +Eu vou também, na verdade, antes de formatar, + +06:49.430 --> 06:49.630 +Eu vou também, na verdade, antes de formatar, + +06:49.630 --> 06:50.190 +Eu vou também, na verdade, antes de formatar, + +06:50.590 --> 06:50.990 +passar por um programa que detecta bad blocks, + +06:50.990 --> 06:51.430 +passar por um programa que detecta bad blocks, + +06:51.430 --> 06:51.570 +passar por um programa que detecta bad blocks, + +06:51.570 --> 06:51.930 +passar por um programa que detecta bad blocks, + +06:51.930 --> 06:52.150 +passar por um programa que detecta bad blocks, + +06:52.150 --> 06:52.610 +passar por um programa que detecta bad blocks, + +06:52.610 --> 06:52.770 +passar por um programa que detecta bad blocks, + +06:52.770 --> 06:53.170 +passar por um programa que detecta bad blocks, + +06:53.170 --> 06:53.670 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:53.670 --> 06:53.850 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:53.850 --> 06:54.250 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:54.250 --> 06:54.830 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:54.830 --> 06:55.110 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:55.110 --> 06:55.530 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:55.530 --> 06:55.670 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:55.670 --> 06:55.790 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:55.790 --> 06:55.890 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:55.890 --> 06:56.010 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:56.010 --> 06:56.250 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:56.250 --> 06:56.410 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:56.410 --> 06:56.950 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:56.950 --> 06:57.150 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:57.150 --> 06:57.270 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:57.270 --> 06:57.510 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:57.510 --> 06:57.890 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:57.890 --> 06:58.210 +verifica a saúde do HD, para eu ter uma noção de o quanto eu consigo confiar nele, + +06:58.810 --> 06:58.890 +e aí depois eu vou utilizá-lo novamente. + +06:58.890 --> 06:59.150 +e depois eu vou utilizá-lo novamente. + +06:59.150 --> 06:59.830 +e aí depois eu vou utilizá-lo novamente. + +06:59.830 --> 07:00.030 +e aí depois eu vou utilizá-lo novamente. + +07:00.030 --> 07:00.270 +e aí depois eu vou utilizá-lo novamente. + +07:00.270 --> 07:01.010 +e aí depois eu vou utilizá-lo novamente. + +07:01.010 --> 07:01.070 +e aí depois eu vou utilizá-lo novamente. + +07:01.070 --> 07:01.570 +e aí depois eu vou utilizá-lo novamente. + +07:01.830 --> 07:01.950 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:01.950 --> 07:02.090 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:02.090 --> 07:02.250 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:02.250 --> 07:02.370 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:02.370 --> 07:02.810 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:02.810 --> 07:03.310 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:03.310 --> 07:03.710 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:03.710 --> 07:04.150 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:04.150 --> 07:04.730 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:04.730 --> 07:05.990 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:05.990 --> 07:06.170 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:06.170 --> 07:06.310 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:06.310 --> 07:06.510 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:06.510 --> 07:06.830 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:06.830 --> 07:07.230 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:07.230 --> 07:07.450 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:07.450 --> 07:07.710 +Ele não vai ser usado para guardar coisas que eu vou ter como única cópia nesse HD. + +07:08.150 --> 07:08.350 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:08.350 --> 07:08.530 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:08.530 --> 07:08.710 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:08.710 --> 07:08.770 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:08.770 --> 07:08.910 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:08.910 --> 07:09.050 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:09.050 --> 07:09.290 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:09.290 --> 07:09.710 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:09.710 --> 07:09.930 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:09.930 --> 07:10.570 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:10.570 --> 07:10.810 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:10.810 --> 07:10.910 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:10.910 --> 07:11.110 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:11.110 --> 07:11.450 +A ideia é que eu tenha mais cópias, porque se o HD falhar, + +07:11.610 --> 07:11.710 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:11.710 --> 07:11.850 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:11.850 --> 07:11.930 +como ele é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:11.930 --> 07:12.010 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:12.010 --> 07:12.070 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:12.070 --> 07:12.250 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:12.250 --> 07:12.690 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:12.690 --> 07:13.050 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:13.050 --> 07:13.170 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:13.170 --> 07:13.250 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:13.250 --> 07:13.370 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:13.370 --> 07:13.470 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:13.470 --> 07:13.770 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:13.770 --> 07:13.910 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:13.910 --> 07:14.050 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:14.050 --> 07:14.350 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:14.350 --> 07:14.590 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:14.590 --> 07:14.870 +como ele já é um HD antigo, eu não vou ter problema em ter perdido esses dados. + +07:15.970 --> 07:16.170 +É isso. Espero que vocês tenham gostado aí. + +07:16.170 --> 07:16.410 +É isso. Espero que vocês tenham gostado aí. + +07:16.410 --> 07:16.690 +É isso. Espero que vocês tenham gostado aí. + +07:16.690 --> 07:17.030 +É isso. Espero que vocês tenham gostado aí. + +07:17.030 --> 07:17.110 +É isso. Espero que vocês tenham gostado aí. + +07:17.110 --> 07:17.230 +É isso. Espero que vocês tenham gostado aí. + +07:17.230 --> 07:17.350 +É isso. Espero que vocês tenham gostado aí. + +07:17.350 --> 07:17.790 +É isso. Espero que vocês tenham gostado aí. + +07:17.790 --> 07:17.990 +É isso. Espero que vocês tenham gostado aí. + +07:18.370 --> 07:18.570 +Qualquer dúvida, pode deixar nos comentários. + +07:18.570 --> 07:18.890 +Qualquer dúvida, pode deixar nos comentários. + +07:18.890 --> 07:19.070 +Qualquer dúvida, pode deixar nos comentários. + +07:19.070 --> 07:19.170 +Qualquer dúvida, pode deixar nos comentários. + +07:19.170 --> 07:19.390 +Qualquer dúvida, pode deixar nos comentários. + +07:19.390 --> 07:19.510 +Qualquer dúvida, pode deixar nos comentários. + +07:19.510 --> 07:19.890 +Qualquer dúvida, pode deixar nos comentários. + diff --git a/tests/data/whisper-words-limpeza-nespresso.vtt b/tests/data/whisper-words-limpeza-nespresso.vtt new file mode 100644 index 0000000..8d269f7 --- /dev/null +++ b/tests/data/whisper-words-limpeza-nespresso.vtt @@ -0,0 +1,1916 @@ +WEBVTT + +00:00.910 --> 00:01.450 +Temos aqui uma cafeteira Nespresso Inicia + +00:01.450 --> 00:01.690 +Temos aqui uma cafeteira Nespresso Inicia + +00:01.690 --> 00:01.910 +Temos aqui uma cafeteira Nespresso Inicia + +00:01.910 --> 00:02.450 +Temos aqui uma cafeteira Nespresso Inicia + +00:02.450 --> 00:03.270 +Temos aqui uma cafeteira Nespresso Inicia + +00:03.270 --> 00:04.170 +Temos aqui uma cafeteira Nespresso Inicia + +00:04.910 --> 00:05.690 +que não inicia a água. + +00:05.690 --> 00:05.950 +que não inicia a água. + +00:05.950 --> 00:06.730 +que não inicia a água. + +00:06.730 --> 00:08.470 +que não inicia a água. + +00:08.470 --> 00:08.850 +que não inicia a água. + +00:09.110 --> 00:09.530 +Vou puxar a água, vou ligar aqui, + +00:09.530 --> 00:09.690 +Vou puxar a água, vou ligar aqui, + +00:09.690 --> 00:09.870 +Vou puxar a água, vou ligar aqui, + +00:09.870 --> 00:10.110 +Vou puxar a água, vou ligar aqui, + +00:10.110 --> 00:12.430 +Vou puxar a água, vou ligar aqui, + +00:12.430 --> 00:12.610 +Vou puxar a água, vou ligar aqui, + +00:12.610 --> 00:12.910 +Vou puxar a água, vou ligar aqui, + +00:12.910 --> 00:13.410 +Vou puxar a água, vou ligar aqui, + +00:15.700 --> 00:15.880 +dar um tempinho para esquentar. + +00:15.880 --> 00:16.020 +dar um tempinho para esquentar. + +00:16.020 --> 00:16.420 +dar um tempinho para esquentar. + +00:16.420 --> 00:16.560 +dar um tempinho para esquentar. + +00:16.560 --> 00:17.080 +dar um tempinho para esquentar. + +00:17.680 --> 00:17.840 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:17.840 --> 00:18.020 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:18.020 --> 00:18.120 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:18.120 --> 00:18.280 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:18.280 --> 00:18.280 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:18.280 --> 00:18.340 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:18.340 --> 00:19.020 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:19.020 --> 00:19.520 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:19.520 --> 00:19.700 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:19.700 --> 00:20.260 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:20.260 --> 00:20.440 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:20.440 --> 00:20.840 +Eu acho que o que está acontecendo com essa cafeteira é que + +00:21.600 --> 00:22.000 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:22.000 --> 00:22.140 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:22.140 --> 00:22.340 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:22.340 --> 00:22.540 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:22.540 --> 00:23.020 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:23.020 --> 00:23.500 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:23.500 --> 00:23.740 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:23.740 --> 00:24.280 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:24.280 --> 00:24.620 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:24.620 --> 00:25.120 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:25.120 --> 00:25.420 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:25.420 --> 00:25.900 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:25.900 --> 00:26.420 +ela ficou muito tempo parada, então a água que estava aqui embaixo + +00:27.520 --> 00:27.920 +ela acabou secando, ficou ar na tubulação + +00:27.920 --> 00:28.720 +ela acabou secando, ficou ar na tubulação + +00:28.720 --> 00:29.560 +ela acabou secando, ficou ar na tubulação + +00:29.560 --> 00:29.700 +ela acabou secando, ficou ar na tubulação + +00:29.700 --> 00:29.900 +ela acabou secando, ficou ar na tubulação + +00:29.900 --> 00:30.260 +ela acabou secando, ficou ar na tubulação + +00:30.260 --> 00:31.020 +ela acabou secando, ficou ar na tubulação + +00:31.020 --> 00:31.640 +ela acabou secando, ficou ar na tubulação + +00:31.640 --> 00:31.920 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:31.920 --> 00:32.200 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:32.200 --> 00:32.380 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:32.380 --> 00:32.680 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:32.680 --> 00:32.960 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:32.960 --> 00:33.180 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:33.180 --> 00:33.460 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:33.460 --> 00:33.760 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:33.760 --> 00:34.600 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:34.600 --> 00:35.060 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:35.060 --> 00:35.240 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:35.240 --> 00:35.680 +e quando ela tenta sugar ela acha que o reservatório está vazio + +00:35.680 --> 00:35.940 +porque só tem ar na tubulação + +00:35.940 --> 00:36.180 +porque tem ar na tubulação + +00:36.180 --> 00:36.360 +porque só tem ar na tubulação + +00:36.360 --> 00:36.540 +porque só tem ar na tubulação + +00:36.540 --> 00:36.680 +porque só tem ar na tubulação + +00:36.680 --> 00:37.260 +porque só tem ar na tubulação + +00:37.920 --> 00:38.460 +e aí ela acaba não conseguindo puxar. + +00:38.460 --> 00:38.660 +e ela acaba não conseguindo puxar. + +00:38.660 --> 00:38.880 +e aí ela acaba não conseguindo puxar. + +00:38.880 --> 00:39.240 +e aí ela acaba não conseguindo puxar. + +00:39.240 --> 00:40.240 +e aí ela acaba não conseguindo puxar. + +00:40.240 --> 00:40.760 +e aí ela acaba não conseguindo puxar. + +00:40.760 --> 00:41.340 +e aí ela acaba não conseguindo puxar. + +00:41.540 --> 00:41.860 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:41.860 --> 00:42.240 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:42.240 --> 00:42.340 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:42.340 --> 00:42.400 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:42.400 --> 00:42.440 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:42.440 --> 00:42.720 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:42.720 --> 00:42.880 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:42.880 --> 00:43.000 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:43.000 --> 00:43.280 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:43.280 --> 00:43.620 +Acho que é isso que aconteceu. Vamos tentar aqui. + +00:44.480 --> 00:44.660 +Vou tirar aqui a bandejinha, + +00:44.660 --> 00:44.860 +Vou tirar aqui a bandejinha, + +00:44.860 --> 00:45.240 +Vou tirar aqui a bandejinha, + +00:45.240 --> 00:46.020 +Vou tirar aqui a bandejinha, + +00:46.020 --> 00:47.300 +Vou tirar aqui a bandejinha, + +00:47.300 --> 00:48.040 +Vou tirar aqui a bandejinha, + +00:48.740 --> 00:48.960 +chegar lá para frente para poder cair na pia. + +00:48.960 --> 00:49.080 +chegar para frente para poder cair na pia. + +00:49.080 --> 00:49.200 +chegar lá para frente para poder cair na pia. + +00:49.200 --> 00:49.580 +chegar lá para frente para poder cair na pia. + +00:49.580 --> 00:49.740 +chegar lá para frente para poder cair na pia. + +00:49.740 --> 00:49.940 +chegar lá para frente para poder cair na pia. + +00:49.940 --> 00:50.120 +chegar lá para frente para poder cair na pia. + +00:50.120 --> 00:50.320 +chegar lá para frente para poder cair na pia. + +00:50.320 --> 00:50.920 +chegar lá para frente para poder cair na pia. + +00:51.500 --> 00:51.660 +Vamos lá. + +00:51.660 --> 00:51.920 +Vamos lá. + +00:53.790 --> 00:54.070 +Não tem cápsula, não sai uma gota de ar. + +00:54.070 --> 00:54.270 +Não tem cápsula, não sai uma gota de ar. + +00:54.270 --> 00:54.750 +Não tem cápsula, não sai uma gota de ar. + +00:54.750 --> 01:04.010 +Não tem cápsula, não sai uma gota de ar. + +01:04.010 --> 01:04.170 +Não tem cápsula, não sai uma gota de ar. + +01:04.170 --> 01:04.430 +Não tem cápsula, não sai uma gota de ar. + +01:04.430 --> 01:04.590 +Não tem cápsula, não sai uma gota de ar. + +01:04.590 --> 01:04.890 +Não tem cápsula, não sai uma gota de ar. + +01:04.890 --> 01:05.050 +Não tem cápsula, não sai uma gota de ar. + +01:05.050 --> 01:05.190 +Não tem cápsula, não sai uma gota de ar. + +01:10.760 --> 01:10.960 +E aqui também não puxa nada. + +01:10.960 --> 01:11.180 +E aqui também não puxa nada. + +01:11.180 --> 01:11.460 +E aqui também não puxa nada. + +01:11.460 --> 01:11.680 +E aqui também não puxa nada. + +01:11.680 --> 01:12.040 +E aqui também não puxa nada. + +01:12.040 --> 01:12.840 +E aqui também não puxa nada. + +01:15.720 --> 01:16.200 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:16.200 --> 01:16.680 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:16.680 --> 01:16.840 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:16.840 --> 01:16.980 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:16.980 --> 01:17.320 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:17.320 --> 01:18.600 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:18.600 --> 01:18.940 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:18.940 --> 01:19.300 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:19.300 --> 01:19.500 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:19.500 --> 01:19.620 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:19.620 --> 01:19.840 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:19.840 --> 01:20.540 +Vamos colocar aqui no modo para tentar fazer com que ela passe + +01:20.540 --> 01:21.000 +toda a água de uma vez só por ali. + +01:21.000 --> 01:21.160 +toda a água de uma vez só por ali. + +01:21.160 --> 01:21.380 +toda a água de uma vez só por ali. + +01:21.380 --> 01:21.500 +toda a água de uma vez só por ali. + +01:21.500 --> 01:21.640 +toda a água de uma vez só por ali. + +01:21.640 --> 01:21.880 +toda a água de uma vez só por ali. + +01:21.880 --> 01:22.160 +toda a água de uma vez por ali. + +01:22.160 --> 01:23.040 +toda a água de uma vez só por ali. + +01:23.040 --> 01:23.220 +toda a água de uma vez só por ali. + +01:23.460 --> 01:23.540 +Tem que segurar os dois botões por 5 segundos + +01:23.540 --> 01:23.640 +Tem que segurar os dois botões por 5 segundos + +01:23.640 --> 01:24.000 +Tem que segurar os dois botões por 5 segundos + +01:24.000 --> 01:24.120 +Tem que segurar os dois botões por 5 segundos + +01:24.120 --> 01:24.280 +Tem que segurar os dois botões por 5 segundos + +01:24.280 --> 01:24.600 +Tem que segurar os dois botões por 5 segundos + +01:24.600 --> 01:24.780 +Tem que segurar os dois botões por 5 segundos + +01:24.780 --> 01:24.940 +Tem que segurar os dois botões por 5 segundos + +01:24.940 --> 01:25.340 +Tem que segurar os dois botões por 5 segundos + +01:26.460 --> 01:26.940 +até eles piscarem. + +01:26.940 --> 01:27.120 +até eles piscarem. + +01:27.120 --> 01:27.680 +até eles piscarem. + +01:31.300 --> 01:31.780 +Começou a piscar rápido. + +01:31.780 --> 01:31.920 +Começou a piscar rápido. + +01:31.920 --> 01:32.260 +Começou a piscar rápido. + +01:32.260 --> 01:33.020 +Começou a piscar rápido. + +01:33.960 --> 01:34.140 +Não é aquela piscada de quando ela liga, + +01:34.140 --> 01:34.280 +Não é aquela piscada de quando ela liga, + +01:34.280 --> 01:34.460 +Não é aquela piscada de quando ela liga, + +01:34.460 --> 01:34.960 +Não é aquela piscada de quando ela liga, + +01:34.960 --> 01:35.100 +Não é aquela piscada de quando ela liga, + +01:35.100 --> 01:35.280 +Não é aquela piscada de quando ela liga, + +01:35.280 --> 01:35.440 +Não é aquela piscada de quando ela liga, + +01:35.440 --> 01:35.760 +Não é aquela piscada de quando ela liga, + +01:35.840 --> 01:35.900 +que ela está aquecendo. + +01:35.900 --> 01:35.980 +que ela está aquecendo. + +01:35.980 --> 01:36.120 +que ela está aquecendo. + +01:36.120 --> 01:36.720 +que ela está aquecendo. + +01:37.420 --> 01:37.700 +Aperta qualquer um dos dois + +01:37.700 --> 01:37.960 +Aperta qualquer um dos dois + +01:37.960 --> 01:38.100 +Aperta qualquer um dos dois + +01:38.100 --> 01:38.240 +Aperta qualquer um dos dois + +01:38.240 --> 01:38.540 +Aperta qualquer um dos dois + +01:39.440 --> 01:39.920 +e ela vai começar a trabalhar + +01:39.920 --> 01:40.000 +e ela vai começar a trabalhar + +01:40.000 --> 01:40.160 +e ela vai começar a trabalhar + +01:40.160 --> 01:40.440 +e ela vai começar a trabalhar + +01:40.440 --> 01:40.580 +e ela vai começar a trabalhar + +01:40.580 --> 01:40.980 +e ela vai começar a trabalhar + +01:41.480 --> 01:41.720 +até que toda essa água seja... + +01:41.720 --> 01:41.880 +até que toda essa água seja... + +01:41.880 --> 01:42.060 +até que toda essa água seja... + +01:42.060 --> 01:42.280 +até que toda essa água seja... + +01:42.280 --> 01:42.540 +até que toda essa água seja... + +01:42.540 --> 01:43.600 +até que toda essa água seja... + +01:45.020 --> 01:45.060 +passe pelo sistema interno dela. + +01:45.060 --> 01:45.400 +passe pelo sistema interno dela. + +01:45.400 --> 01:45.980 +passe pelo sistema interno dela. + +01:45.980 --> 01:46.360 +passe pelo sistema interno dela. + +01:46.360 --> 01:46.620 +passe pelo sistema interno dela. + +01:58.660 --> 01:59.600 +Ainda sem nada. + +01:59.600 --> 01:59.880 +Ainda sem nada. + +01:59.880 --> 02:00.240 +Ainda sem nada. + +02:02.790 --> 02:03.230 +Parece realmente ser problema de ar ali dentro. + +02:03.230 --> 02:03.710 +Parece realmente ser problema de ar ali dentro. + +02:03.710 --> 02:03.850 +Parece realmente ser problema de ar ali dentro. + +02:03.850 --> 02:04.150 +Parece realmente ser problema de ar ali dentro. + +02:04.150 --> 02:04.350 +Parece realmente ser problema de ar ali dentro. + +02:04.350 --> 02:04.790 +Parece realmente ser problema de ar ali dentro. + +02:04.790 --> 02:05.650 +Parece realmente ser problema de ar ali dentro. + +02:05.650 --> 02:06.090 +Parece realmente ser problema de ar ali dentro. + +02:07.110 --> 02:07.190 +E a solução que eu vi, + +02:07.190 --> 02:07.290 +E a solução que eu vi, + +02:07.290 --> 02:07.590 +E a solução que eu vi, + +02:07.590 --> 02:07.750 +E a solução que eu vi, + +02:07.750 --> 02:07.890 +E a solução que eu vi, + +02:07.890 --> 02:08.030 +E a solução que eu vi, + +02:08.470 --> 02:08.590 +que algumas pessoas fizeram, + +02:08.590 --> 02:08.810 +que algumas pessoas fizeram, + +02:08.810 --> 02:09.110 +que algumas pessoas fizeram, + +02:09.110 --> 02:09.810 +que algumas pessoas fizeram, + +02:10.330 --> 02:10.650 +foi injetar água aqui com uma seringa + +02:10.650 --> 02:11.310 +foi injetar água aqui com uma seringa + +02:11.310 --> 02:11.590 +foi injetar água aqui com uma seringa + +02:11.590 --> 02:11.870 +foi injetar água aqui com uma seringa + +02:11.870 --> 02:12.210 +foi injetar água aqui com uma seringa + +02:12.210 --> 02:12.370 +foi injetar água aqui com uma seringa + +02:12.370 --> 02:12.750 +foi injetar água aqui com uma seringa + +02:12.750 --> 02:12.990 +para poder entrar com pressão + +02:12.990 --> 02:13.210 +para poder entrar com pressão + +02:13.210 --> 02:13.450 +para poder entrar com pressão + +02:13.450 --> 02:13.650 +para poder entrar com pressão + +02:13.650 --> 02:14.170 +para poder entrar com pressão + +02:14.730 --> 02:15.290 +e aí com isso ela consegue + +02:15.290 --> 02:15.410 +e com isso ela consegue + +02:15.410 --> 02:15.550 +e aí com isso ela consegue + +02:15.550 --> 02:15.630 +e aí com isso ela consegue + +02:15.630 --> 02:15.790 +e aí com isso ela consegue + +02:15.790 --> 02:16.310 +e aí com isso ela consegue + +02:16.910 --> 02:17.470 +passar pela camada de ar + +02:17.470 --> 02:17.730 +passar pela camada de ar + +02:17.730 --> 02:18.090 +passar pela camada de ar + +02:18.090 --> 02:18.290 +passar pela camada de ar + +02:18.290 --> 02:18.470 +passar pela camada de ar + +02:18.470 --> 02:19.530 +e a máquina consegue continuar o ciclo. + +02:19.530 --> 02:19.650 +e a máquina consegue continuar o ciclo. + +02:19.650 --> 02:19.870 +e a máquina consegue continuar o ciclo. + +02:19.870 --> 02:20.350 +e a máquina consegue continuar o ciclo. + +02:20.350 --> 02:20.870 +e a máquina consegue continuar o ciclo. + +02:20.870 --> 02:21.070 +e a máquina consegue continuar o ciclo. + +02:21.070 --> 02:21.350 +e a máquina consegue continuar o ciclo. + +02:22.490 --> 02:22.890 +Como eu não tenho uma seringa aqui, + +02:22.890 --> 02:22.990 +Como eu não tenho uma seringa aqui, + +02:22.990 --> 02:23.070 +Como eu não tenho uma seringa aqui, + +02:23.070 --> 02:23.210 +Como eu não tenho uma seringa aqui, + +02:23.210 --> 02:23.390 +Como eu não tenho uma seringa aqui, + +02:23.390 --> 02:23.770 +Como eu não tenho uma seringa aqui, + +02:23.770 --> 02:23.970 +Como eu não tenho uma seringa aqui, + +02:25.370 --> 02:25.530 +eu vou tentar simplesmente + +02:25.530 --> 02:25.610 +eu vou tentar simplesmente + +02:25.610 --> 02:25.850 +eu vou tentar simplesmente + +02:25.850 --> 02:26.490 +eu vou tentar simplesmente + +02:28.510 --> 02:29.070 +ter certeza de que eu tenho água + +02:29.070 --> 02:29.570 +ter certeza de que eu tenho água + +02:29.570 --> 02:29.770 +ter certeza de que eu tenho água + +02:29.770 --> 02:29.950 +ter certeza de que eu tenho água + +02:29.950 --> 02:30.250 +ter certeza de que eu tenho água + +02:30.250 --> 02:30.430 +ter certeza de que eu tenho água + +02:30.430 --> 02:31.010 +ter certeza de que eu tenho água + +02:31.010 --> 02:31.410 +aqui nessa parte, + +02:31.410 --> 02:31.690 +aqui nessa parte, + +02:31.690 --> 02:32.190 +aqui nessa parte, + +02:33.890 --> 02:34.090 +um pouquinho aqui com a leiteira, + +02:34.090 --> 02:34.410 +um pouquinho aqui com a leiteira, + +02:34.410 --> 02:34.610 +um pouquinho aqui com a leiteira, + +02:34.610 --> 02:34.730 +um pouquinho aqui com a leiteira, + +02:34.730 --> 02:34.830 +um pouquinho aqui com a leiteira, + +02:34.830 --> 02:35.310 +um pouquinho aqui com a leiteira, + +02:40.420 --> 02:40.560 +ter certeza de que tem água chegando ali + +02:40.560 --> 02:41.020 +ter certeza de que tem água chegando ali + +02:41.020 --> 02:41.220 +ter certeza de que tem água chegando ali + +02:41.220 --> 02:41.420 +ter certeza de que tem água chegando ali + +02:41.420 --> 02:41.840 +ter certeza de que tem água chegando ali + +02:41.840 --> 02:42.160 +ter certeza de que tem água chegando ali + +02:42.160 --> 02:42.700 +ter certeza de que tem água chegando ali + +02:42.700 --> 02:42.940 +ter certeza de que tem água chegando ali + +02:43.540 --> 02:43.840 +e daí eu vou apertar o botão + +02:43.840 --> 02:43.960 +e daí eu vou apertar o botão + +02:43.960 --> 02:44.080 +e daí eu vou apertar o botão + +02:44.080 --> 02:44.180 +e daí eu vou apertar o botão + +02:44.180 --> 02:44.520 +e daí eu vou apertar o botão + +02:44.520 --> 02:44.620 +e daí eu vou apertar o botão + +02:44.620 --> 02:44.940 +e daí eu vou apertar o botão + +02:45.840 --> 02:46.440 +para ela começar a puxar. + +02:46.440 --> 02:46.560 +para ela começar a puxar. + +02:46.560 --> 02:46.880 +para ela começar a puxar. + +02:46.880 --> 02:47.080 +para ela começar a puxar. + +02:47.080 --> 02:47.460 +para ela começar a puxar. + +02:47.860 --> 02:48.380 +Vou tentar dar umas apertadas aqui + +02:48.380 --> 02:48.620 +Vou tentar dar umas apertadas aqui + +02:48.620 --> 02:48.780 +Vou tentar dar umas apertadas aqui + +02:48.780 --> 02:48.960 +Vou tentar dar umas apertadas aqui + +02:48.960 --> 02:49.500 +Vou tentar dar umas apertadas aqui + +02:49.500 --> 02:49.800 +Vou tentar dar umas apertadas aqui + +02:49.800 --> 02:50.680 +para ver se ela consegue... + +02:50.680 --> 02:50.800 +para ver se ela consegue... + +02:50.800 --> 02:50.920 +para ver se ela consegue... + +02:50.920 --> 02:51.040 +para ver se ela consegue... + +02:51.040 --> 02:51.820 +para ver se ela consegue... + +02:51.820 --> 02:51.980 +a água consegue entrar ali no circuito. + +02:51.980 --> 02:52.160 +a água consegue entrar ali no circuito. + +02:52.160 --> 02:52.540 +a água consegue entrar ali no circuito. + +02:52.540 --> 02:52.840 +a água consegue entrar ali no circuito. + +02:52.840 --> 02:53.020 +a água consegue entrar ali no circuito. + +02:53.020 --> 02:53.160 +a água consegue entrar ali no circuito. + +02:53.160 --> 02:53.700 +a água consegue entrar ali no circuito. + +03:00.250 --> 03:00.630 +Puxou. + +03:01.930 --> 03:02.530 +Puxou a água. + +03:02.530 --> 03:02.650 +Puxou a água. + +03:02.650 --> 03:02.870 +Puxou a água. + +03:03.070 --> 03:03.250 +Ainda não saiu por aqui, + +03:03.250 --> 03:03.470 +Ainda não saiu por aqui, + +03:03.470 --> 03:03.750 +Ainda não saiu por aqui, + +03:03.750 --> 03:03.950 +Ainda não saiu por aqui, + +03:03.950 --> 03:04.170 +Ainda não saiu por aqui, + +03:05.690 --> 03:05.930 +mas agora eu acredito que + +03:05.930 --> 03:06.150 +mas agora eu acredito que + +03:06.150 --> 03:06.290 +mas agora eu acredito que + +03:06.290 --> 03:06.650 +mas agora eu acredito que + +03:06.650 --> 03:06.970 +mas agora eu acredito que + +03:08.250 --> 03:08.850 +colocando aqui + +03:08.850 --> 03:09.190 +colocando aqui + +03:09.850 --> 03:10.150 +ela conseguiu continuar o processo. + +03:10.150 --> 03:10.550 +ela conseguiu continuar o processo. + +03:10.550 --> 03:10.910 +ela conseguiu continuar o processo. + +03:10.910 --> 03:11.070 +ela conseguiu continuar o processo. + +03:11.070 --> 03:11.490 +ela conseguiu continuar o processo. + +03:14.480 --> 03:15.120 +Está lá, funcionando. + +03:15.120 --> 03:15.360 +Está lá, funcionando. + +03:15.360 --> 03:15.800 +Está lá, funcionando. + +03:15.800 --> 03:16.600 +Está lá, funcionando. + +03:18.560 --> 03:18.880 +Agora é só fazer uma limpezinha nela + +03:18.880 --> 03:18.980 +Agora é só fazer uma limpezinha nela + +03:18.980 --> 03:19.060 +Agora é fazer uma limpezinha nela + +03:19.060 --> 03:19.280 +Agora é só fazer uma limpezinha nela + +03:19.280 --> 03:19.420 +Agora é só fazer uma limpezinha nela + +03:19.420 --> 03:19.940 +Agora é só fazer uma limpezinha nela + +03:19.940 --> 03:20.260 +Agora é só fazer uma limpezinha nela + +03:21.420 --> 03:22.060 +e voltar a tomar os cafés. + +03:22.060 --> 03:22.320 +e voltar a tomar os cafés. + +03:22.320 --> 03:22.500 +e voltar a tomar os cafés. + +03:22.500 --> 03:22.580 +e voltar a tomar os cafés. + +03:22.580 --> 03:22.780 +e voltar a tomar os cafés. + +03:22.780 --> 03:23.200 +e voltar a tomar os cafés. + +03:24.080 --> 03:24.680 +O processo de limpeza aqui + +03:24.680 --> 03:25.000 +O processo de limpeza aqui + +03:25.000 --> 03:25.180 +O processo de limpeza aqui + +03:25.180 --> 03:25.720 +O processo de limpeza aqui + +03:25.720 --> 03:26.040 +O processo de limpeza aqui + +03:26.740 --> 03:27.380 +desse bico das cafeteiras Nespresso, + +03:27.380 --> 03:27.860 +desse bico das cafeteiras Nespresso, + +03:27.860 --> 03:28.540 +desse bico das cafeteiras Nespresso, + +03:28.540 --> 03:29.040 +desse bico das cafeteiras Nespresso, + +03:29.040 --> 03:29.560 +desse bico das cafeteiras Nespresso, + +03:29.660 --> 03:29.700 +que às vezes pode acumular aqui + +03:29.700 --> 03:29.840 +que às vezes pode acumular aqui + +03:29.840 --> 03:29.900 +que às vezes pode acumular aqui + +03:29.900 --> 03:30.260 +que às vezes pode acumular aqui + +03:30.260 --> 03:31.420 +que às vezes pode acumular aqui + +03:31.420 --> 03:31.740 +que às vezes pode acumular aqui + +03:31.740 --> 03:32.360 +um pouco de borra de café + +03:32.360 --> 03:32.500 +um pouco de borra de café + +03:32.500 --> 03:32.720 +um pouco de borra de café + +03:32.720 --> 03:32.960 +um pouco de borra de café + +03:32.960 --> 03:33.120 +um pouco de borra de café + +03:33.120 --> 03:33.440 +um pouco de borra de café + +03:33.440 --> 03:34.080 +e aí o fluxo sai menor. + +03:34.080 --> 03:34.300 +e o fluxo sai menor. + +03:34.300 --> 03:34.660 +e aí o fluxo sai menor. + +03:34.660 --> 03:35.120 +e aí o fluxo sai menor. + +03:35.120 --> 03:35.300 +e aí o fluxo sai menor. + +03:35.300 --> 03:35.600 +e aí o fluxo sai menor. + +03:36.040 --> 03:36.140 +Ela acaba vazando mais aqui para dentro. + +03:36.140 --> 03:36.460 +Ela acaba vazando mais aqui para dentro. + +03:36.460 --> 03:37.080 +Ela acaba vazando mais aqui para dentro. + +03:37.080 --> 03:37.380 +Ela acaba vazando mais aqui para dentro. + +03:37.380 --> 03:37.640 +Ela acaba vazando mais aqui para dentro. + +03:37.640 --> 03:38.080 +Ela acaba vazando mais aqui para dentro. + +03:38.080 --> 03:38.480 +Ela acaba vazando mais aqui para dentro. + +03:39.100 --> 03:39.300 +A água da bandeja enche rápido. + +03:39.300 --> 03:39.620 +A água da bandeja enche rápido. + +03:39.620 --> 03:39.960 +A água da bandeja enche rápido. + +03:39.960 --> 03:40.340 +A água da bandeja enche rápido. + +03:40.340 --> 03:40.620 +A água da bandeja enche rápido. + +03:40.620 --> 03:41.000 +A água da bandeja enche rápido. + +03:41.500 --> 03:41.940 +E nem sempre + +03:41.940 --> 03:42.140 +E nem sempre + +03:42.140 --> 03:42.620 +E nem sempre + +03:42.620 --> 03:43.240 + passar água por ela + +03:43.240 --> 03:43.620 +só passar água por ela + +03:43.620 --> 03:43.980 +só passar água por ela + +03:43.980 --> 03:44.220 +só passar água por ela + +03:44.220 --> 03:44.440 +só passar água por ela + +03:44.440 --> 03:44.680 +sem a cápsula resolve. + +03:44.680 --> 03:44.860 +sem a cápsula resolve. + +03:44.860 --> 03:45.260 +sem a cápsula resolve. + +03:45.260 --> 03:45.780 +sem a cápsula resolve. + +03:46.340 --> 03:46.560 +Tem muita coisa que fica impregnada aqui. + +03:46.560 --> 03:46.800 +Tem muita coisa que fica impregnada aqui. + +03:46.800 --> 03:47.000 +Tem muita coisa que fica impregnada aqui. + +03:47.000 --> 03:47.100 +Tem muita coisa que fica impregnada aqui. + +03:47.100 --> 03:47.220 +Tem muita coisa que fica impregnada aqui. + +03:47.220 --> 03:47.820 +Tem muita coisa que fica impregnada aqui. + +03:47.820 --> 03:48.040 +Tem muita coisa que fica impregnada aqui. + +03:48.660 --> 03:48.820 +Uma maneira que eu vi, + +03:48.820 --> 03:49.060 +Uma maneira que eu vi, + +03:49.060 --> 03:49.220 +Uma maneira que eu vi, + +03:49.220 --> 03:49.380 +Uma maneira que eu vi, + +03:49.380 --> 03:49.520 +Uma maneira que eu vi, + +03:49.740 --> 03:49.860 +que eu inclusive achei no YouTube, + +03:49.860 --> 03:49.940 +que eu inclusive achei no YouTube, + +03:49.940 --> 03:50.280 +que eu inclusive achei no YouTube, + +03:50.280 --> 03:50.920 +que eu inclusive achei no YouTube, + +03:50.920 --> 03:51.060 +que eu inclusive achei no YouTube, + +03:51.060 --> 03:51.400 +que eu inclusive achei no YouTube, + +03:52.160 --> 03:52.380 +é o seguinte, tira aqui o reservatório, + +03:52.380 --> 03:52.560 +é o seguinte, tira aqui o reservatório, + +03:52.560 --> 03:52.700 +é o seguinte, tira aqui o reservatório, + +03:52.700 --> 03:53.040 +é o seguinte, tira aqui o reservatório, + +03:53.040 --> 03:53.200 +é o seguinte, tira aqui o reservatório, + +03:53.200 --> 03:53.440 +é o seguinte, tira aqui o reservatório, + +03:53.440 --> 03:53.860 +é o seguinte, tira aqui o reservatório, + +03:53.860 --> 03:54.660 +é o seguinte, tira aqui o reservatório, + +03:54.840 --> 03:55.020 +tira toda a parte aqui, + +03:55.020 --> 03:55.200 +tira toda a parte aqui, + +03:55.200 --> 03:55.340 +tira toda a parte aqui, + +03:55.340 --> 03:55.500 +tira toda a parte aqui, + +03:55.500 --> 03:55.680 +tira toda a parte aqui, + +03:55.820 --> 03:56.020 +não tem cápsula aqui dentro, + +03:56.020 --> 03:56.200 +não tem cápsula aqui dentro, + +03:56.200 --> 03:56.520 +não tem cápsula aqui dentro, + +03:56.520 --> 03:56.680 +não tem cápsula aqui dentro, + +03:56.680 --> 03:56.840 +não tem cápsula aqui dentro, + +03:56.940 --> 03:57.080 +deixa ela aberta, + +03:57.080 --> 03:57.220 +deixa ela aberta, + +03:57.220 --> 03:57.600 +deixa ela aberta, + +03:58.620 --> 03:58.740 +você vai virar ela de cabeça para baixo, + +03:58.740 --> 03:59.040 +você vai virar ela de cabeça para baixo, + +03:59.040 --> 04:00.020 +você vai virar ela de cabeça para baixo, + +04:00.020 --> 04:00.240 +você vai virar ela de cabeça para baixo, + +04:00.240 --> 04:00.420 +você vai virar ela de cabeça para baixo, + +04:00.420 --> 04:00.720 +você vai virar ela de cabeça para baixo, + +04:00.720 --> 04:00.960 +você vai virar ela de cabeça para baixo, + +04:00.960 --> 04:01.440 +você vai virar ela de cabeça para baixo, + +04:02.460 --> 04:02.640 +se eu consigo mostrar aqui, + +04:02.640 --> 04:02.740 +se eu consigo mostrar aqui, + +04:02.740 --> 04:02.940 +se eu consigo mostrar aqui, + +04:02.940 --> 04:03.260 +se eu consigo mostrar aqui, + +04:03.260 --> 04:03.480 +se eu consigo mostrar aqui, + +04:04.540 --> 04:04.980 +inclinar para frente, + +04:04.980 --> 04:05.420 +inclinar para frente, + +04:05.420 --> 04:06.000 +inclinar para frente, + +04:06.340 --> 04:06.540 +ou seja, não é para ela ficar + +04:06.540 --> 04:06.780 +ou seja, não é para ela ficar + +04:06.780 --> 04:06.880 +ou seja, não é para ela ficar + +04:06.880 --> 04:07.060 +ou seja, não é para ela ficar + +04:07.060 --> 04:07.220 +ou seja, não é para ela ficar + +04:07.220 --> 04:07.320 +ou seja, não é para ela ficar + +04:07.320 --> 04:07.500 +ou seja, não é para ela ficar + +04:07.500 --> 04:07.760 +ou seja, não é para ela ficar + +04:07.760 --> 04:07.880 +na posição totalmente aqui + +04:07.880 --> 04:08.280 +na posição totalmente aqui + +04:08.280 --> 04:08.980 +na posição totalmente aqui + +04:08.980 --> 04:09.740 +na posição totalmente aqui + +04:09.740 --> 04:10.160 +em 90 graus, + +04:10.160 --> 04:11.320 +em 90 graus, + +04:11.320 --> 04:11.740 +em 90 graus, + +04:11.800 --> 04:11.860 +é para uma leve inclinação na frente, + +04:11.860 --> 04:11.980 +é para uma leve inclinação na frente, + +04:11.980 --> 04:12.760 +é para uma leve inclinação na frente, + +04:12.760 --> 04:13.060 +é para uma leve inclinação na frente, + +04:13.060 --> 04:13.480 +é para uma leve inclinação na frente, + +04:13.480 --> 04:13.680 +é para uma leve inclinação na frente, + +04:13.680 --> 04:13.960 +é para uma leve inclinação na frente, + +04:14.160 --> 04:14.320 +porque a gente vai jogar água + +04:14.320 --> 04:15.220 +porque a gente vai jogar água + +04:15.220 --> 04:15.260 +porque a gente vai jogar água + +04:15.260 --> 04:15.380 +porque a gente vai jogar água + +04:15.380 --> 04:15.660 +porque a gente vai jogar água + +04:15.660 --> 04:16.200 +porque a gente vai jogar água + +04:16.200 --> 04:16.500 +com a torneira, + +04:16.500 --> 04:16.640 +com a torneira, + +04:16.640 --> 04:17.080 +com a torneira, + +04:17.740 --> 04:17.840 +se possível água quente, + +04:17.840 --> 04:18.160 +se possível água quente, + +04:18.160 --> 04:18.360 +se possível água quente, + +04:18.360 --> 04:18.760 +se possível água quente, + +04:18.920 --> 04:19.020 +com a maior pressão possível, aqui no bico. + +04:19.020 --> 04:19.100 +com a maior pressão possível, aqui no bico. + +04:19.100 --> 04:19.120 +com a maior pressão possível, aqui no bico. + +04:19.120 --> 04:19.480 +com a maior pressão possível, aqui no bico. + +04:19.480 --> 04:19.920 +com a maior pressão possível, aqui no bico. + +04:19.920 --> 04:20.060 +com a maior pressão possível, aqui no bico. + +04:20.060 --> 04:20.260 +com a maior pressão possível, aqui no bico. + +04:20.260 --> 04:20.440 +com a maior pressão possível, aqui no bico. + +04:20.440 --> 04:20.700 +com a maior pressão possível, aqui no bico. + +04:21.580 --> 04:21.680 +E aí a água vai passar por dentro do bico + +04:21.680 --> 04:21.860 +E a água vai passar por dentro do bico + +04:21.860 --> 04:22.400 +E aí a água vai passar por dentro do bico + +04:22.400 --> 04:22.640 +E aí a água vai passar por dentro do bico + +04:22.640 --> 04:22.960 +E aí a água vai passar por dentro do bico + +04:22.960 --> 04:23.340 +E aí a água vai passar por dentro do bico + +04:23.340 --> 04:23.560 +E aí a água vai passar por dentro do bico + +04:23.560 --> 04:23.720 +E aí a água vai passar por dentro do bico + +04:23.720 --> 04:23.880 +E aí a água vai passar por dentro do bico + +04:23.880 --> 04:24.120 +E aí a água vai passar por dentro do bico + +04:24.120 --> 04:24.440 +e sair ali por cima. + +04:24.440 --> 04:24.700 +e sair ali por cima. + +04:24.700 --> 04:25.380 +e sair ali por cima. + +04:25.380 --> 04:25.580 +e sair ali por cima. + +04:25.580 --> 04:25.820 +e sair ali por cima. + +04:26.880 --> 04:27.400 +É importante inclinar + +04:27.400 --> 04:27.740 +É importante inclinar + +04:27.740 --> 04:28.000 +É importante inclinar + +04:28.340 --> 04:28.840 +justamente para que a água + +04:28.840 --> 04:29.080 +justamente para que a água + +04:29.080 --> 04:29.200 +justamente para que a água + +04:29.200 --> 04:29.280 +justamente para que a água + +04:29.280 --> 04:29.460 +justamente para que a água + +04:29.460 --> 04:29.700 +não entre na máquina, + +04:29.700 --> 04:30.400 +não entre na máquina, + +04:30.400 --> 04:31.580 +não entre na máquina, + +04:31.580 --> 04:31.940 +não entre na máquina, + +04:32.080 --> 04:32.120 +para que ela caia ali na pia. + +04:32.120 --> 04:32.220 +para que ela caia ali na pia. + +04:32.220 --> 04:32.340 +para que ela caia ali na pia. + +04:32.340 --> 04:32.780 +para que ela caia ali na pia. + +04:32.780 --> 04:33.200 +para que ela caia ali na pia. + +04:33.200 --> 04:33.700 +para que ela caia ali na pia. + +04:33.700 --> 04:33.920 +para que ela caia ali na pia. + +04:34.620 --> 04:35.280 +Então, estou com a máquina aqui, + +04:35.280 --> 04:35.460 +Então, estou com a máquina aqui, + +04:35.460 --> 04:35.640 +Então, estou com a máquina aqui, + +04:35.640 --> 04:35.740 +Então, estou com a máquina aqui, + +04:35.740 --> 04:35.820 +Então, estou com a máquina aqui, + +04:35.820 --> 04:36.060 +Então, estou com a máquina aqui, + +04:36.060 --> 04:36.400 +Então, estou com a máquina aqui, + +04:37.140 --> 04:37.360 +vou inclinar, + +04:37.360 --> 04:38.240 +vou inclinar, + +04:39.380 --> 04:39.800 +água quente, + +04:39.800 --> 04:40.280 +água quente, + +04:41.380 --> 04:42.040 +na maior pressão possível, + +04:42.040 --> 04:42.240 +na maior pressão possível, + +04:42.240 --> 04:42.640 +na maior pressão possível, + +04:42.640 --> 04:43.060 +na maior pressão possível, + +05:11.460 --> 05:11.880 +esperar escorrer tudo, + +05:11.880 --> 05:12.360 +esperar escorrer tudo, + +05:12.360 --> 05:12.760 +esperar escorrer tudo, + +05:15.860 --> 05:16.000 +e daí podemos virar novamente + +05:16.000 --> 05:16.120 +e daí podemos virar novamente + +05:16.120 --> 05:16.440 +e daí podemos virar novamente + +05:16.440 --> 05:16.780 +e daí podemos virar novamente + +05:16.780 --> 05:17.300 +e daí podemos virar novamente + +05:17.300 --> 05:17.760 +a nossa máquina. + +05:17.760 --> 05:17.960 +a nossa máquina. + +05:17.960 --> 05:18.300 +a nossa máquina. + +05:18.860 --> 05:19.180 +Esse procedimento também funciona + +05:19.180 --> 05:19.740 +Esse procedimento também funciona + +05:19.740 --> 05:19.980 +Esse procedimento também funciona + +05:19.980 --> 05:20.340 +Esse procedimento também funciona + +05:20.340 --> 05:20.520 +com outras máquinas Nespresso, + +05:20.520 --> 05:20.880 +com outras máquinas Nespresso, + +05:20.880 --> 05:21.300 +com outras máquinas Nespresso, + +05:21.300 --> 05:22.000 +com outras máquinas Nespresso, + +05:22.100 --> 05:22.220 +com esse tipo de cápsula, + +05:22.220 --> 05:22.360 +com esse tipo de cápsula, + +05:22.360 --> 05:22.520 +com esse tipo de cápsula, + +05:22.520 --> 05:22.700 +com esse tipo de cápsula, + +05:22.700 --> 05:23.060 +com esse tipo de cápsula, + +05:23.680 --> 05:23.860 +e fica aqui a dica para quem quiser comprar + +05:23.860 --> 05:24.100 +e fica aqui a dica para quem quiser comprar + +05:24.100 --> 05:24.280 +e fica aqui a dica para quem quiser comprar + +05:24.280 --> 05:24.420 +e fica aqui a dica para quem quiser comprar + +05:24.420 --> 05:24.680 +e fica aqui a dica para quem quiser comprar + +05:24.680 --> 05:25.020 +e fica aqui a dica para quem quiser comprar + +05:25.020 --> 05:25.200 +e fica aqui a dica para quem quiser comprar + +05:25.200 --> 05:25.400 +e fica aqui a dica para quem quiser comprar + +05:25.400 --> 05:25.700 +e fica aqui a dica para quem quiser comprar + +05:25.700 --> 05:26.320 +cápsulas de café especial, + +05:26.320 --> 05:26.700 +cápsulas de café especial, + +05:26.700 --> 05:26.940 +cápsulas de café especial, + +05:26.940 --> 05:27.700 +cápsulas de café especial, + +05:27.880 --> 05:28.100 +micro lotes, em vários tipos, + +05:28.100 --> 05:28.760 +micro lotes, em vários tipos, + +05:28.760 --> 05:29.160 +micro lotes, em vários tipos, + +05:29.160 --> 05:29.460 +micro lotes, em vários tipos, + +05:29.460 --> 05:29.700 +micro lotes, em vários tipos, + +05:29.700 --> 05:30.140 +micro lotes, em vários tipos, + +05:30.740 --> 05:30.960 +entra aqui no site do Crio Café, + +05:30.960 --> 05:31.140 +entra aqui no site do Crio Café, + +05:31.140 --> 05:31.280 +entra aqui no site do Crio Café, + +05:31.280 --> 05:31.540 +entra aqui no site do Crio Café, + +05:31.540 --> 05:31.780 +entra aqui no site do Crio Café, + +05:31.780 --> 05:32.140 +entra aqui no site do Crio Café, + +05:32.140 --> 05:32.640 +entra aqui no site do Crio Café, + +05:32.640 --> 05:33.100 +que é uma cafeteria e refação de São Paulo, + +05:33.100 --> 05:33.180 +que é uma cafeteria e refação de São Paulo, + +05:33.180 --> 05:33.320 +que é uma cafeteria e refação de São Paulo, + +05:33.320 --> 05:33.860 +que é uma cafeteria e refação de São Paulo, + +05:33.860 --> 05:34.120 +que é uma cafeteria e refação de São Paulo, + +05:34.120 --> 05:34.540 +que é uma cafeteria e refação de São Paulo, + +05:34.540 --> 05:34.700 +que é uma cafeteria e refação de São Paulo, + +05:34.700 --> 05:34.920 +que é uma cafeteria e refação de São Paulo, + +05:34.920 --> 05:35.220 +que é uma cafeteria e refação de São Paulo, + +05:35.360 --> 05:35.400 +mas que entrega para todo o Brasil, + +05:35.400 --> 05:35.540 +mas que entrega para todo o Brasil, + +05:35.540 --> 05:35.960 +mas que entrega para todo o Brasil, + +05:35.960 --> 05:36.100 +mas que entrega para todo o Brasil, + +05:36.100 --> 05:36.300 +mas que entrega para todo o Brasil, + +05:36.300 --> 05:36.400 +mas que entrega para todo o Brasil, + +05:36.400 --> 05:36.760 +mas que entrega para todo o Brasil, + +05:37.300 --> 05:37.360 +e tem inclusive assinatura, + +05:37.360 --> 05:37.540 +e tem inclusive assinatura, + +05:37.540 --> 05:37.980 +e tem inclusive assinatura, + +05:37.980 --> 05:38.700 +e tem inclusive assinatura, + +05:38.860 --> 05:39.080 +café com cápsula, café em grão, moído, + +05:39.080 --> 05:39.300 +café com cápsula, café em grão, moído, + +05:39.300 --> 05:39.720 +café com cápsula, café em grão, moído, + +05:39.720 --> 05:39.840 +café com cápsula, café em grão, moído, + +05:39.840 --> 05:39.980 +café com cápsula, café em grão, moído, + +05:39.980 --> 05:40.120 +café com cápsula, café em grão, moído, + +05:40.120 --> 05:40.360 +café com cápsula, café em grão, moído, + +05:40.360 --> 05:40.500 +café com cápsula, café em grão, moído, + +05:40.500 --> 05:40.920 +café com cápsula, café em grão, moído, + +05:41.580 --> 05:41.680 +e um monte de outras coisas gostosas. + +05:41.680 --> 05:41.760 +e um monte de outras coisas gostosas. + +05:41.760 --> 05:41.940 +e um monte de outras coisas gostosas. + +05:41.940 --> 05:42.080 +e um monte de outras coisas gostosas. + +05:42.080 --> 05:42.260 +e um monte de outras coisas gostosas. + +05:42.260 --> 05:42.520 +e um monte de outras coisas gostosas. + +05:42.520 --> 05:43.040 +e um monte de outras coisas gostosas. + +05:43.380 --> 05:43.700 +Valeu! + diff --git a/tests/data/youtube-auto-hd-notebook.vtt b/tests/data/youtube-auto-hd-notebook.vtt new file mode 100644 index 0000000..f560c86 --- /dev/null +++ b/tests/data/youtube-auto-hd-notebook.vtt @@ -0,0 +1,1288 @@ +WEBVTT +Kind: captions +Language: pt + +00:00:01.250 --> 00:00:04.130 align:start position:0% + +muita<00:00:02.250> gente<00:00:02.370> não<00:00:02.700> sabe<00:00:02.909> o<00:00:03.179> que<00:00:03.510><00:00:03.840> para<00:00:03.959> usar + +00:00:04.130 --> 00:00:04.140 align:start position:0% +muita gente não sabe o que dá para usar + + +00:00:04.140 --> 00:00:07.070 align:start position:0% +muita gente não sabe o que dá para usar +o<00:00:04.290> hd<00:00:04.620> de<00:00:04.710> notebook<00:00:04.830> como<00:00:05.609> um<00:00:06.060> hd<00:00:06.299> externo + +00:00:07.070 --> 00:00:07.080 align:start position:0% +o hd de notebook como um hd externo + + +00:00:07.080 --> 00:00:09.049 align:start position:0% +o hd de notebook como um hd externo +na<00:00:07.740> verdade<00:00:08.010><00:00:08.160> pra<00:00:08.280> usar<00:00:08.340> a<00:00:08.610> de<00:00:08.730> desktops + +00:00:09.049 --> 00:00:09.059 align:start position:0% +na verdade dá pra usar a de desktops + + +00:00:09.059 --> 00:00:12.290 align:start position:0% +na verdade dá pra usar a de desktops +também<00:00:09.450> com<00:00:09.660> um<00:00:09.780> hd<00:00:09.990> externo<00:00:10.130> e<00:00:11.179> muitas<00:00:12.179> vezes + +00:00:12.290 --> 00:00:12.300 align:start position:0% +também com um hd externo e muitas vezes + + +00:00:12.300 --> 00:00:15.200 align:start position:0% +também com um hd externo e muitas vezes +quando<00:00:12.690> o<00:00:12.900> sistema<00:00:13.139> não<00:00:13.469> está<00:00:13.740> iniciando<00:00:14.280> é + +00:00:15.200 --> 00:00:15.210 align:start position:0% +quando o sistema não está iniciando é + + +00:00:15.210 --> 00:00:18.080 align:start position:0% +quando o sistema não está iniciando é +provavelmente<00:00:16.109> um<00:00:16.619> problema<00:00:17.100> do<00:00:17.190> hd<00:00:17.520> existem + +00:00:18.080 --> 00:00:18.090 align:start position:0% +provavelmente um problema do hd existem + + +00:00:18.090 --> 00:00:20.630 align:start position:0% +provavelmente um problema do hd existem +obviamente<00:00:18.359> outras<00:00:19.350> possibilidades<00:00:20.220> mas + +00:00:20.630 --> 00:00:20.640 align:start position:0% +obviamente outras possibilidades mas + + +00:00:20.640 --> 00:00:22.070 align:start position:0% +obviamente outras possibilidades mas +quando<00:00:20.850> você<00:00:21.060> liga<00:00:21.300><00:00:21.480> por<00:00:21.630> exemplo<00:00:21.720> que<00:00:22.020> o + +00:00:22.070 --> 00:00:22.080 align:start position:0% +quando você liga lá por exemplo que o + + +00:00:22.080 --> 00:00:24.439 align:start position:0% +quando você liga lá por exemplo que o +windows<00:00:22.260> trava<00:00:22.769> na<00:00:22.859> tela<00:00:23.100> inicial<00:00:23.189> e<00:00:24.090> nunca + +00:00:24.439 --> 00:00:24.449 align:start position:0% +windows trava na tela inicial e nunca + + +00:00:24.449 --> 00:00:24.920 align:start position:0% +windows trava na tela inicial e nunca +anda + +00:00:24.920 --> 00:00:24.930 align:start position:0% +anda + + +00:00:24.930 --> 00:00:26.779 align:start position:0% +anda +pode<00:00:25.470> ser<00:00:25.650> que<00:00:25.740> algum<00:00:25.920> arquivo<00:00:26.099> tenha<00:00:26.609> sido + +00:00:26.779 --> 00:00:26.789 align:start position:0% +pode ser que algum arquivo tenha sido + + +00:00:26.789 --> 00:00:29.300 align:start position:0% +pode ser que algum arquivo tenha sido +corrompido<00:00:27.480> e<00:00:27.840> o<00:00:27.960> hd<00:00:28.289><00:00:28.380> estejam<00:00:28.890> nos<00:00:28.980> dias + +00:00:29.300 --> 00:00:29.310 align:start position:0% +corrompido e o hd já estejam nos dias + + +00:00:29.310 --> 00:00:31.099 align:start position:0% +corrompido e o hd já estejam nos dias +finais<00:00:29.429> de<00:00:29.789> vida<00:00:30.029> dele<00:00:30.359> mas<00:00:30.689> isso<00:00:30.900> não<00:00:30.990> quer + +00:00:31.099 --> 00:00:31.109 align:start position:0% +finais de vida dele mas isso não quer + + +00:00:31.109 --> 00:00:33.350 align:start position:0% +finais de vida dele mas isso não quer +dizer<00:00:31.170> que<00:00:31.320> você<00:00:31.439> perdeu<00:00:31.710> os<00:00:31.890> dados<00:00:32.040> é<00:00:32.820> você + +00:00:33.350 --> 00:00:33.360 align:start position:0% +dizer que você perdeu os dados é você + + +00:00:33.360 --> 00:00:36.319 align:start position:0% +dizer que você perdeu os dados é você +pode<00:00:33.630> tirar<00:00:33.780> o<00:00:34.050> hd<00:00:34.380> conectá<00:00:35.370> lo<00:00:35.610> numa<00:00:35.880> case + +00:00:36.319 --> 00:00:36.329 align:start position:0% +pode tirar o hd conectá lo numa case + + +00:00:36.329 --> 00:00:39.470 align:start position:0% +pode tirar o hd conectá lo numa case +dessa<00:00:36.660> de<00:00:36.809> hd<00:00:37.410> externo<00:00:37.860> é<00:00:38.219> pro<00:00:38.850> hd<00:00:39.180> de<00:00:39.360> notebook + +00:00:39.470 --> 00:00:39.480 align:start position:0% +dessa de hd externo é pro hd de notebook + + +00:00:39.480 --> 00:00:43.400 align:start position:0% +dessa de hd externo é pro hd de notebook +e<00:00:39.930> um<00:00:39.989> hd<00:00:40.170> de<00:00:40.290> 2<00:00:40.620> 6<00:00:40.829> polegadas<00:00:41.010> e<00:00:41.520> daí<00:00:42.420> é<00:00:42.960> você + +00:00:43.400 --> 00:00:43.410 align:start position:0% +e um hd de 2 6 polegadas e daí é você + + +00:00:43.410 --> 00:00:45.889 align:start position:0% +e um hd de 2 6 polegadas e daí é você +conseguir<00:00:43.620> ligar<00:00:44.340> esse<00:00:44.640> hd<00:00:44.850> via<00:00:45.210> usb<00:00:45.539> como<00:00:45.780> se + +00:00:45.889 --> 00:00:45.899 align:start position:0% +conseguir ligar esse hd via usb como se + + +00:00:45.899 --> 00:00:47.380 align:start position:0% +conseguir ligar esse hd via usb como se +fosse<00:00:46.050> um<00:00:46.110> pendrive<00:00:46.440> no<00:00:46.890> outro<00:00:47.160> computador + +00:00:47.380 --> 00:00:47.390 align:start position:0% +fosse um pendrive no outro computador + + +00:00:47.390 --> 00:00:50.600 align:start position:0% +fosse um pendrive no outro computador +copiar<00:00:48.390> os<00:00:48.510> dados<00:00:48.570> de<00:00:49.200> formatar<00:00:49.649> o<00:00:49.739> hd + +00:00:50.600 --> 00:00:50.610 align:start position:0% +copiar os dados de formatar o hd + + +00:00:50.610 --> 00:00:53.270 align:start position:0% +copiar os dados de formatar o hd +então<00:00:50.879> essa<00:00:51.539> é<00:00:51.600> uma<00:00:51.629> dica<00:00:51.989> para<00:00:52.280> reaproveitar + +00:00:53.270 --> 00:00:53.280 align:start position:0% +então essa é uma dica para reaproveitar + + +00:00:53.280 --> 00:00:55.340 align:start position:0% +então essa é uma dica para reaproveitar +água<00:00:53.370> desde<00:00:53.610> o<00:00:53.670> notebook<00:00:53.940> seja<00:00:54.750> porque<00:00:54.989> você + +00:00:55.340 --> 00:00:55.350 align:start position:0% +água desde o notebook seja porque você + + +00:00:55.350 --> 00:00:57.350 align:start position:0% +água desde o notebook seja porque você +não<00:00:55.920> consegue<00:00:56.070> abrir<00:00:56.460> o<00:00:56.550> sistema<00:00:56.670> seja<00:00:57.149> porque + +00:00:57.350 --> 00:00:57.360 align:start position:0% +não consegue abrir o sistema seja porque + + +00:00:57.360 --> 00:01:00.619 align:start position:0% +não consegue abrir o sistema seja porque +você<00:00:57.629> trocou<00:00:58.530> de<00:00:58.620> computador<00:00:59.100> é<00:00:59.879> que<00:01:00.359> ia<00:01:00.449> fazer + +00:01:00.619 --> 00:01:00.629 align:start position:0% +você trocou de computador é que ia fazer + + +00:01:00.629 --> 00:01:02.420 align:start position:0% +você trocou de computador é que ia fazer +um<00:01:00.690> backup<00:01:00.870><00:01:01.410> lembra<00:01:01.710> o<00:01:01.739> seguinte<00:01:02.100> conforme + +00:01:02.420 --> 00:01:02.430 align:start position:0% +um backup só lembra o seguinte conforme + + +00:01:02.430 --> 00:01:05.420 align:start position:0% +um backup só lembra o seguinte conforme +o<00:01:02.640> hd<00:01:03.059> vai<00:01:03.449> sendo<00:01:03.719> utilizado<00:01:04.470> é<00:01:04.769> a<00:01:05.159> tendência + +00:01:05.420 --> 00:01:05.430 align:start position:0% +o hd vai sendo utilizado é a tendência + + +00:01:05.430 --> 00:01:07.580 align:start position:0% +o hd vai sendo utilizado é a tendência +que<00:01:05.670> de<00:01:05.790> costume<00:01:06.049> porque<00:01:07.049> ele<00:01:07.200> começa<00:01:07.409> a + +00:01:07.580 --> 00:01:07.590 align:start position:0% +que de costume porque ele começa a + + +00:01:07.590 --> 00:01:10.940 align:start position:0% +que de costume porque ele começa a +falhar<00:01:07.830> e<00:01:08.549> pode<00:01:09.360> ser<00:01:09.630> que<00:01:09.810> não<00:01:10.470> seja<00:01:10.650> tão + +00:01:10.940 --> 00:01:10.950 align:start position:0% +falhar e pode ser que não seja tão + + +00:01:10.950 --> 00:01:12.859 align:start position:0% +falhar e pode ser que não seja tão +seguro<00:01:11.159> assim<00:01:11.400> guardar<00:01:11.880> os<00:01:12.000> dados<00:01:12.119> nesse<00:01:12.540> hd + +00:01:12.859 --> 00:01:12.869 align:start position:0% +seguro assim guardar os dados nesse hd + + +00:01:12.869 --> 00:01:15.350 align:start position:0% +seguro assim guardar os dados nesse hd +antigo<00:01:13.350> então<00:01:14.130> não<00:01:14.340> recomendo<00:01:14.850> por<00:01:14.970> exemplo + +00:01:15.350 --> 00:01:15.360 align:start position:0% +antigo então não recomendo por exemplo + + +00:01:15.360 --> 00:01:18.170 align:start position:0% +antigo então não recomendo por exemplo +sexo<00:01:15.799> sou<00:01:16.799> o<00:01:16.830> único<00:01:17.280> lugar<00:01:17.549> onde<00:01:17.729> você<00:01:18.030> vai + +00:01:18.170 --> 00:01:18.180 align:start position:0% +sexo sou o único lugar onde você vai + + +00:01:18.180 --> 00:01:21.830 align:start position:0% +sexo sou o único lugar onde você vai +guardar<00:01:18.479> alguns<00:01:18.689> arquivos<00:01:19.080> é<00:01:20.420> essa<00:01:21.420> coisa<00:01:21.600> que + +00:01:21.830 --> 00:01:21.840 align:start position:0% +guardar alguns arquivos é essa coisa que + + +00:01:21.840 --> 00:01:23.300 align:start position:0% +guardar alguns arquivos é essa coisa que +eu<00:01:21.930> comprei<00:01:22.290> no<00:01:22.380> mercado<00:01:22.710> livre + +00:01:23.300 --> 00:01:23.310 align:start position:0% +eu comprei no mercado livre + + +00:01:23.310 --> 00:01:25.120 align:start position:0% +eu comprei no mercado livre +eu<00:01:23.430> estou<00:01:23.640> deixando<00:01:23.759> link<00:01:24.119> na<00:01:24.180> discussão<00:01:24.570> com + +00:01:25.120 --> 00:01:25.130 align:start position:0% +eu estou deixando link na discussão com + + +00:01:25.130 --> 00:01:27.530 align:start position:0% +eu estou deixando link na discussão com +compra<00:01:26.130> a<00:01:26.159> compra<00:01:26.580> vai<00:01:27.030> custar<00:01:27.240> em<00:01:27.299> torno<00:01:27.330> de + +00:01:27.530 --> 00:01:27.540 align:start position:0% +compra a compra vai custar em torno de + + +00:01:27.540 --> 00:01:30.590 align:start position:0% +compra a compra vai custar em torno de +uns<00:01:27.960> vinte<00:01:28.259> reais<00:01:28.439> e<00:01:29.159> ela<00:01:29.729> vem<00:01:30.090><00:01:30.270> com<00:01:30.479> tudo + +00:01:30.590 --> 00:01:30.600 align:start position:0% +uns vinte reais e ela vem já com tudo + + +00:01:30.600 --> 00:01:32.840 align:start position:0% +uns vinte reais e ela vem já com tudo +que<00:01:30.750> você<00:01:30.810> precisa<00:01:31.049> inclusive<00:01:31.590> com<00:01:31.860> a<00:01:31.950> chave + +00:01:32.840 --> 00:01:32.850 align:start position:0% +que você precisa inclusive com a chave + + +00:01:32.850 --> 00:01:36.920 align:start position:0% +que você precisa inclusive com a chave +escuta<00:01:33.270> aqui<00:01:33.299> ó<00:01:34.189> solução<00:01:35.189> tal<00:01:35.549> como<00:01:36.000> a<00:01:36.090> chegar + +00:01:36.920 --> 00:01:36.930 align:start position:0% +escuta aqui ó solução tal como a chegar + + +00:01:36.930 --> 00:01:41.810 align:start position:0% +escuta aqui ó solução tal como a chegar +aqui<00:01:38.930> ela<00:01:39.930> é<00:01:40.320> relativamente<00:01:40.799> pequena<00:01:41.520> simples + +00:01:41.810 --> 00:01:41.820 align:start position:0% +aqui ela é relativamente pequena simples + + +00:01:41.820 --> 00:01:44.179 align:start position:0% +aqui ela é relativamente pequena simples +tem<00:01:42.090> algumas<00:01:42.390> são<00:01:42.540> mais<00:01:42.630> bonitas<00:01:43.350> que<00:01:44.070> eu<00:01:44.130> + +00:01:44.179 --> 00:01:44.189 align:start position:0% +tem algumas são mais bonitas que eu já + + +00:01:44.189 --> 00:01:47.510 align:start position:0% +tem algumas são mais bonitas que eu já +vinha<00:01:44.430> tampinha<00:01:44.939> a<00:01:45.030> case<00:01:45.479> ih<00:01:46.189> eu<00:01:47.189> preferi + +00:01:47.510 --> 00:01:47.520 align:start position:0% +vinha tampinha a case ih eu preferi + + +00:01:47.520 --> 00:01:49.700 align:start position:0% +vinha tampinha a case ih eu preferi +comprar<00:01:47.759> a<00:01:48.180> mais<00:01:48.450> barata<00:01:48.720> porque<00:01:49.200> neste<00:01:49.680> caso + +00:01:49.700 --> 00:01:49.710 align:start position:0% +comprar a mais barata porque neste caso + + +00:01:49.710 --> 00:01:51.170 align:start position:0% +comprar a mais barata porque neste caso +não<00:01:49.950> queria<00:01:50.130> gastar<00:01:50.430> muito<00:01:50.670> dinheiro<00:01:50.759> com<00:01:51.149> a + +00:01:51.170 --> 00:01:51.180 align:start position:0% +não queria gastar muito dinheiro com a + + +00:01:51.180 --> 00:01:53.679 align:start position:0% +não queria gastar muito dinheiro com a +chegada<00:01:51.479> de<00:01:51.540> wen<00:01:52.079> uma<00:01:52.200> chavezinha<00:01:52.470> philips + +00:01:53.679 --> 00:01:53.689 align:start position:0% +chegada de wen uma chavezinha philips + + +00:01:53.689 --> 00:01:56.539 align:start position:0% +chegada de wen uma chavezinha philips +para<00:01:54.689> ajudar<00:01:55.079> aqui<00:01:55.229> não<00:01:55.320> estava<00:01:55.619><00:01:55.770> vêm<00:01:56.490> o + +00:01:56.539 --> 00:01:56.549 align:start position:0% +para ajudar aqui não estava só vêm o + + +00:01:56.549 --> 00:01:59.899 align:start position:0% +para ajudar aqui não estava só vêm o +cabo<00:01:57.560> esse<00:01:58.560> carro<00:01:58.799> aqui<00:01:58.890> é<00:01:59.250> um<00:01:59.280> cabo<00:01:59.670> que<00:01:59.759> tem + +00:01:59.899 --> 00:01:59.909 align:start position:0% +cabo esse carro aqui é um cabo que tem + + +00:01:59.909 --> 00:02:02.090 align:start position:0% +cabo esse carro aqui é um cabo que tem +de<00:02:00.540> um<00:02:00.600> em<00:02:00.869> uma<00:02:01.079> ponta + +00:02:02.090 --> 00:02:02.100 align:start position:0% +de um em uma ponta + + +00:02:02.100 --> 00:02:06.080 align:start position:0% +de um em uma ponta +ele<00:02:02.790> tem<00:02:03.180> um<00:02:03.750> conector<00:02:04.110> que<00:02:04.979> esse<00:02:05.280> daqui<00:02:05.520> que + +00:02:06.080 --> 00:02:06.090 align:start position:0% +ele tem um conector que esse daqui que + + +00:02:06.090 --> 00:02:08.960 align:start position:0% +ele tem um conector que esse daqui que +vai<00:02:06.180> no<00:02:06.420> hd<00:02:06.750> na<00:02:07.170> case<00:02:07.469> e<00:02:08.069> na<00:02:08.250> outra<00:02:08.399> ponta<00:02:08.729> tem + +00:02:08.960 --> 00:02:08.970 align:start position:0% +vai no hd na case e na outra ponta tem + + +00:02:08.970 --> 00:02:10.400 align:start position:0% +vai no hd na case e na outra ponta tem +dois<00:02:09.179> conectores<00:02:09.390> usb + +00:02:10.400 --> 00:02:10.410 align:start position:0% +dois conectores usb + + +00:02:10.410 --> 00:02:12.979 align:start position:0% +dois conectores usb +vou<00:02:10.860> ter<00:02:10.979> que<00:02:11.009> ligar<00:02:11.160> um<00:02:11.310> computador<00:02:11.489> um<00:02:12.330> vai + +00:02:12.979 --> 00:02:12.989 align:start position:0% +vou ter que ligar um computador um vai + + +00:02:12.989 --> 00:02:14.510 align:start position:0% +vou ter que ligar um computador um vai +dar<00:02:13.170> vai<00:02:13.620> ser<00:02:13.770> mais<00:02:13.950> responsável<00:02:14.400> pela + +00:02:14.510 --> 00:02:14.520 align:start position:0% +dar vai ser mais responsável pela + + +00:02:14.520 --> 00:02:15.890 align:start position:0% +dar vai ser mais responsável pela +energia<00:02:14.910> outro<00:02:15.120> pela<00:02:15.270> transferência<00:02:15.569> de + +00:02:15.890 --> 00:02:15.900 align:start position:0% +energia outro pela transferência de + + +00:02:15.900 --> 00:02:16.699 align:start position:0% +energia outro pela transferência de +dados + +00:02:16.699 --> 00:02:16.709 align:start position:0% +dados + + +00:02:16.709 --> 00:02:21.460 align:start position:0% +dados +anda<00:02:17.280> aqui<00:02:17.520> dentro<00:02:17.970> tem<00:02:18.200> um<00:02:19.200> pacotinho<00:02:19.950> de + +00:02:21.460 --> 00:02:21.470 align:start position:0% +anda aqui dentro tem um pacotinho de + + +00:02:21.470 --> 00:02:23.960 align:start position:0% +anda aqui dentro tem um pacotinho de +parafusos<00:02:22.470> 12<00:02:22.770> parafusos<00:02:23.040> que<00:02:23.340> não<00:02:23.520> parece + +00:02:23.960 --> 00:02:23.970 align:start position:0% +parafusos 12 parafusos que não parece + + +00:02:23.970 --> 00:02:27.770 align:start position:0% +parafusos 12 parafusos que não parece +mas<00:02:24.120> tem<00:02:24.300> dois<00:02:24.450> parafusos<00:02:24.780> aqui<00:02:25.970> é<00:02:26.970> eu<00:02:27.569> vou + +00:02:27.770 --> 00:02:27.780 align:start position:0% +mas tem dois parafusos aqui é eu vou + + +00:02:27.780 --> 00:02:30.770 align:start position:0% +mas tem dois parafusos aqui é eu vou +então<00:02:28.099> começar<00:02:29.099> basicamente<00:02:29.940> é<00:02:30.480> muito + +00:02:30.770 --> 00:02:30.780 align:start position:0% +então começar basicamente é muito + + +00:02:30.780 --> 00:02:33.290 align:start position:0% +então começar basicamente é muito +simples<00:02:31.110> o<00:02:31.140> processo<00:02:31.819> simplesmente<00:02:32.819> vai + +00:02:33.290 --> 00:02:33.300 align:start position:0% +simples o processo simplesmente vai + + +00:02:33.300 --> 00:02:35.869 align:start position:0% +simples o processo simplesmente vai +conectar<00:02:33.959> aqui<00:02:34.170><00:02:34.800> encaixa<00:02:35.160> de<00:02:35.280> um<00:02:35.310> lado<00:02:35.670> não + +00:02:35.869 --> 00:02:35.879 align:start position:0% +conectar aqui só encaixa de um lado não + + +00:02:35.879 --> 00:02:37.940 align:start position:0% +conectar aqui só encaixa de um lado não +tem<00:02:36.120> um<00:02:36.239> conector<00:02:36.540> menor<00:02:37.110> ou<00:02:37.230> maior + +00:02:37.940 --> 00:02:37.950 align:start position:0% +tem um conector menor ou maior + + +00:02:37.950 --> 00:02:40.190 align:start position:0% +tem um conector menor ou maior +olhando<00:02:38.459> o<00:02:38.489> programa<00:02:38.879> tem<00:02:39.090> o<00:02:39.120> menor<00:02:39.390> ou<00:02:39.569> maior + +00:02:40.190 --> 00:02:40.200 align:start position:0% +olhando o programa tem o menor ou maior + + +00:02:40.200 --> 00:02:42.619 align:start position:0% +olhando o programa tem o menor ou maior +então<00:02:40.470> tem<00:02:40.620> que<00:02:40.650> vir<00:02:41.220> aqui<00:02:41.390> pra<00:02:42.390> encaixar + +00:02:42.619 --> 00:02:42.629 align:start position:0% +então tem que vir aqui pra encaixar + + +00:02:42.629 --> 00:02:43.820 align:start position:0% +então tem que vir aqui pra encaixar +direitinho + +00:02:43.820 --> 00:02:43.830 align:start position:0% +direitinho + + +00:02:43.830 --> 00:02:50.800 align:start position:0% +direitinho +daí<00:02:44.160> eu<00:02:44.540> encaixo<00:02:48.440> agora<00:02:49.440> é<00:02:49.500><00:02:49.650> fechar<00:02:50.190> aquele + +00:02:50.800 --> 00:02:50.810 align:start position:0% +daí eu encaixo agora é só fechar aquele + + +00:02:50.810 --> 00:02:55.309 align:start position:0% +daí eu encaixo agora é só fechar aquele +então<00:02:53.120> obviamente<00:02:54.120> outras<00:02:54.840> coisas<00:02:55.080> vão<00:02:55.200> ser + +00:02:55.309 --> 00:02:55.319 align:start position:0% +então obviamente outras coisas vão ser + + +00:02:55.319 --> 00:02:56.720 align:start position:0% +então obviamente outras coisas vão ser +diferentes<00:02:55.349> mas<00:02:55.769> é<00:02:55.920> basicamente<00:02:56.160> isso<00:02:56.489> você + +00:02:56.720 --> 00:02:56.730 align:start position:0% +diferentes mas é basicamente isso você + + +00:02:56.730 --> 00:02:59.030 align:start position:0% +diferentes mas é basicamente isso você +vai<00:02:56.819> ter<00:02:56.940> que<00:02:57.030> é<00:02:57.690> conectar<00:02:58.349> no<00:02:58.470> circuito<00:02:58.950> da + +00:02:59.030 --> 00:02:59.040 align:start position:0% +vai ter que é conectar no circuito da + + +00:02:59.040 --> 00:03:03.559 align:start position:0% +vai ter que é conectar no circuito da +case<00:02:59.480> colocar<00:03:00.480> o<00:03:00.569> hd<00:03:00.840> daquele<00:03:01.560> e<00:03:02.330> fechar<00:03:03.330> aqui + +00:03:03.559 --> 00:03:03.569 align:start position:0% +case colocar o hd daquele e fechar aqui + + +00:03:03.569 --> 00:03:05.690 align:start position:0% +case colocar o hd daquele e fechar aqui +não<00:03:04.140> tem<00:03:04.459> segredo + +00:03:05.690 --> 00:03:05.700 align:start position:0% +não tem segredo + + +00:03:05.700 --> 00:03:08.449 align:start position:0% +não tem segredo +além<00:03:05.849> disso<00:03:06.530> essa<00:03:07.530> daqui<00:03:07.680> por<00:03:08.010> ser<00:03:08.250> mais + +00:03:08.449 --> 00:03:08.459 align:start position:0% +além disso essa daqui por ser mais + + +00:03:08.459 --> 00:03:10.399 align:start position:0% +além disso essa daqui por ser mais +barata<00:03:08.909> perceber<00:03:09.360> que<00:03:09.480> tem<00:03:09.629> em<00:03:09.900> alguns + +00:03:10.399 --> 00:03:10.409 align:start position:0% +barata perceber que tem em alguns + + +00:03:10.409 --> 00:03:13.280 align:start position:0% +barata perceber que tem em alguns +modelos<00:03:10.709> têm<00:03:11.069> uma<00:03:11.280> certa<00:03:11.690> diferença<00:03:12.690> que + +00:03:13.280 --> 00:03:13.290 align:start position:0% +modelos têm uma certa diferença que + + +00:03:13.290 --> 00:03:30.940 align:start position:0% +modelos têm uma certa diferença que +entre<00:03:15.980> o<00:03:16.980> tamanho<00:03:17.670> da<00:03:17.879> que<00:03:18.090> tantas<00:03:18.870> vezes<00:03:28.010> é + +00:03:30.940 --> 00:03:30.950 align:start position:0% +entre o tamanho da que tantas vezes é + + +00:03:30.950 --> 00:03:33.380 align:start position:0% +entre o tamanho da que tantas vezes é +bom<00:03:31.950> aqui<00:03:32.489> agora<00:03:32.640> é<00:03:32.760><00:03:32.849> colocar<00:03:33.090> um<00:03:33.150> parafuso + +00:03:33.380 --> 00:03:33.390 align:start position:0% +bom aqui agora é só colocar um parafuso + + +00:03:33.390 --> 00:03:37.240 align:start position:0% +bom aqui agora é só colocar um parafuso +esse<00:03:33.690> lado<00:03:33.989> para<00:03:34.769> o<00:03:35.190> outro<00:03:35.220> lado + +00:03:37.240 --> 00:03:37.250 align:start position:0% +esse lado para o outro lado + + +00:03:37.250 --> 00:03:40.750 align:start position:0% +esse lado para o outro lado +[Música] + +00:03:40.750 --> 00:03:40.760 align:start position:0% + + + +00:03:40.760 --> 00:03:43.789 align:start position:0% + +depois<00:03:41.760> de<00:03:41.819> usar<00:03:42.209><00:03:43.110> vai<00:03:43.260> estar<00:03:43.379> pronto<00:03:43.440> é<00:03:43.769> + +00:03:43.789 --> 00:03:43.799 align:start position:0% +depois de usar já vai estar pronto é só + + +00:03:43.799 --> 00:03:45.140 align:start position:0% +depois de usar já vai estar pronto é só +ligar<00:03:44.159> um<00:03:44.220> computador + +00:03:45.140 --> 00:03:45.150 align:start position:0% +ligar um computador + + +00:03:45.150 --> 00:03:49.580 align:start position:0% +ligar um computador +daí<00:03:45.959> o<00:03:47.629> brasil<00:03:48.629> esteja<00:03:49.110> utilizando<00:03:49.170> o<00:03:49.560> windows + +00:03:49.580 --> 00:03:49.590 align:start position:0% +daí o brasil esteja utilizando o windows + + +00:03:49.590 --> 00:03:56.320 align:start position:0% +daí o brasil esteja utilizando o windows +lima<00:03:50.280> gnu<00:03:50.700> linux<00:03:53.420> você<00:03:54.420><00:03:54.689> vai<00:03:54.780> conseguir + +00:03:56.320 --> 00:03:56.330 align:start position:0% +lima gnu linux você já vai conseguir + + +00:03:56.330 --> 00:03:59.210 align:start position:0% +lima gnu linux você já vai conseguir +abrir<00:03:57.330> uma<00:03:57.360> solução<00:03:57.720> pendrive<00:03:58.080> mesmo<00:03:58.590><00:03:58.980> toma + +00:03:59.210 --> 00:03:59.220 align:start position:0% +abrir uma solução pendrive mesmo só toma + + +00:03:59.220 --> 00:04:01.759 align:start position:0% +abrir uma solução pendrive mesmo só toma +cuidado<00:03:59.700> que<00:04:00.209> caso<00:04:00.750> tenha<00:04:01.049> algum<00:04:01.230> problema<00:04:01.409> na + +00:04:01.759 --> 00:04:01.769 align:start position:0% +cuidado que caso tenha algum problema na + + +00:04:01.769 --> 00:04:06.229 align:start position:0% +cuidado que caso tenha algum problema na +partição<00:04:02.250> do<00:04:03.030> do<00:04:03.599> seu<00:04:03.810> hd<00:04:03.959><00:04:04.409> porque<00:04:05.220> ela<00:04:05.640> se + +00:04:06.229 --> 00:04:06.239 align:start position:0% +partição do do seu hd pô porque ela se + + +00:04:06.239 --> 00:04:07.640 align:start position:0% +partição do do seu hd pô porque ela se +por<00:04:06.450> exemplo<00:04:06.510> o<00:04:06.720> sistema<00:04:06.810> não<00:04:07.080> está<00:04:07.230> iniciando + +00:04:07.640 --> 00:04:07.650 align:start position:0% +por exemplo o sistema não está iniciando + + +00:04:07.650 --> 00:04:10.460 align:start position:0% +por exemplo o sistema não está iniciando +e<00:04:08.519> por<00:04:09.239> isso<00:04:09.540> você<00:04:09.870> quer<00:04:10.080> recuperar<00:04:10.409> os + +00:04:10.460 --> 00:04:10.470 align:start position:0% +e por isso você quer recuperar os + + +00:04:10.470 --> 00:04:12.589 align:start position:0% +e por isso você quer recuperar os +arquivos<00:04:10.790> cuidado<00:04:11.790> que<00:04:11.879> no<00:04:12.030> windows<00:04:12.180> ele + +00:04:12.589 --> 00:04:12.599 align:start position:0% +arquivos cuidado que no windows ele + + +00:04:12.599 --> 00:04:15.320 align:start position:0% +arquivos cuidado que no windows ele +costuma<00:04:12.930> sugerir<00:04:13.189> formatar<00:04:14.189> dado<00:04:15.060> que<00:04:15.180> ele + +00:04:15.320 --> 00:04:15.330 align:start position:0% +costuma sugerir formatar dado que ele + + +00:04:15.330 --> 00:04:16.969 align:start position:0% +costuma sugerir formatar dado que ele +não<00:04:15.510> consegue<00:04:15.930> às<00:04:16.109> vezes<00:04:16.290> se<00:04:16.500> identificar + +00:04:16.969 --> 00:04:16.979 align:start position:0% +não consegue às vezes se identificar + + +00:04:16.979 --> 00:04:19.699 align:start position:0% +não consegue às vezes se identificar +partição<00:04:17.340> não<00:04:18.209> faça<00:04:18.540> isso<00:04:18.989> essa<00:04:19.139> informação + +00:04:19.699 --> 00:04:19.709 align:start position:0% +partição não faça isso essa informação + + +00:04:19.709 --> 00:04:23.120 align:start position:0% +partição não faça isso essa informação +vai<00:04:19.799> perder<00:04:20.130> os<00:04:20.220> seus<00:04:20.310> dados<00:04:20.489> e<00:04:22.040> não<00:04:23.040> é + +00:04:23.120 --> 00:04:23.130 align:start position:0% +vai perder os seus dados e não é + + +00:04:23.130 --> 00:04:25.800 align:start position:0% +vai perder os seus dados e não é +conseguir<00:04:23.990> depois + +00:04:25.800 --> 00:04:25.810 align:start position:0% +conseguir depois + + +00:04:25.810 --> 00:04:28.379 align:start position:0% +conseguir depois +tão<00:04:26.139> facilmente<00:04:26.560> recuperá<00:04:26.980> los<00:04:27.610> nesse<00:04:27.910> caso + +00:04:28.379 --> 00:04:28.389 align:start position:0% +tão facilmente recuperá los nesse caso + + +00:04:28.389 --> 00:04:30.720 align:start position:0% +tão facilmente recuperá los nesse caso +surgir<00:04:29.020> utilizar<00:04:29.530> um<00:04:29.620> sistema<00:04:29.800> mais<00:04:30.639> robusto + +00:04:30.720 --> 00:04:30.730 align:start position:0% +surgir utilizar um sistema mais robusto + + +00:04:30.730 --> 00:04:33.690 align:start position:0% +surgir utilizar um sistema mais robusto +como<00:04:31.120> bino<00:04:31.510> linux<00:04:31.900> conquistar<00:04:32.889> pontos + +00:04:33.690 --> 00:04:33.700 align:start position:0% +como bino linux conquistar pontos + + +00:04:33.700 --> 00:04:36.210 align:start position:0% +como bino linux conquistar pontos +basta<00:04:34.300> então<00:04:34.510> agora<00:04:34.720> pegar<00:04:35.080> esse<00:04:35.230> lado<00:04:35.500> do<00:04:35.650> do + +00:04:36.210 --> 00:04:36.220 align:start position:0% +basta então agora pegar esse lado do do + + +00:04:36.220 --> 00:04:38.610 align:start position:0% +basta então agora pegar esse lado do do +cabo<00:04:36.490> que<00:04:36.580><00:04:36.639> tem<00:04:36.850> um<00:04:36.880> conector<00:04:37.620> conectar + +00:04:38.610 --> 00:04:38.620 align:start position:0% +cabo que só tem um conector conectar + + +00:04:38.620 --> 00:04:44.370 align:start position:0% +cabo que só tem um conector conectar +aqui<00:04:39.750> e<00:04:40.750><00:04:40.990> vai<00:04:41.530> ser<00:04:41.620><00:04:41.830> ligar<00:04:42.760> no<00:04:42.850> computador + +00:04:44.370 --> 00:04:44.380 align:start position:0% +aqui e aí vai ser só ligar no computador + + +00:04:44.380 --> 00:04:47.129 align:start position:0% +aqui e aí vai ser só ligar no computador +estou<00:04:44.710> aqui<00:04:44.800> no<00:04:44.889> computador<00:04:45.280> agora<00:04:45.520> é<00:04:46.330> pra + +00:04:47.129 --> 00:04:47.139 align:start position:0% +estou aqui no computador agora é pra + + +00:04:47.139 --> 00:04:48.840 align:start position:0% +estou aqui no computador agora é pra +quem<00:04:47.230> tiver<00:04:47.470> curiosidade<00:04:47.680> e<00:04:48.070> dosando<00:04:48.340> devem + +00:04:48.840 --> 00:04:48.850 align:start position:0% +quem tiver curiosidade e dosando devem + + +00:04:48.850 --> 00:04:50.790 align:start position:0% +quem tiver curiosidade e dosando devem +seguir<00:04:48.970> no<00:04:49.090> linux<00:04:49.360> e<00:04:49.810> um<00:04:50.050> gerenciador<00:04:50.560> de + +00:04:50.790 --> 00:04:50.800 align:start position:0% +seguir no linux e um gerenciador de + + +00:04:50.800 --> 00:04:52.920 align:start position:0% +seguir no linux e um gerenciador de +janelas<00:04:50.919> é<00:04:51.400> o<00:04:51.520> e3<00:04:52.120> w + +00:04:52.920 --> 00:04:52.930 align:start position:0% +janelas é o e3 w + + +00:04:52.930 --> 00:04:56.700 align:start position:0% +janelas é o e3 w +recomendo<00:04:53.860> pra<00:04:53.980> caraca<00:04:54.400> uso<00:04:54.760> desde<00:04:54.970> 2012 + +00:04:56.700 --> 00:04:56.710 align:start position:0% +recomendo pra caraca uso desde 2012 + + +00:04:56.710 --> 00:04:58.620 align:start position:0% +recomendo pra caraca uso desde 2012 +vamos<00:04:56.980><00:04:57.070> eu<00:04:57.310> vou<00:04:57.400> lugar<00:04:57.760> que<00:04:57.850> os<00:04:57.970> dois<00:04:58.180> cabos + +00:04:58.620 --> 00:04:58.630 align:start position:0% +vamos lá eu vou lugar que os dois cabos + + +00:04:58.630 --> 00:05:04.500 align:start position:0% +vamos lá eu vou lugar que os dois cabos +usb<00:04:59.320> do<00:04:59.979> hd<00:05:00.639> externo<00:05:01.120> porque<00:05:01.389> isso<00:05:03.510> deve + +00:05:04.500 --> 00:05:04.510 align:start position:0% +usb do hd externo porque isso deve + + +00:05:04.510 --> 00:05:07.200 align:start position:0% +usb do hd externo porque isso deve +aparecer<00:05:05.050> aqui<00:05:05.169> do<00:05:05.290> ladinho<00:05:05.650> o<00:05:06.310> novo<00:05:06.940> volume + +00:05:07.200 --> 00:05:07.210 align:start position:0% +aparecer aqui do ladinho o novo volume + + +00:05:07.210 --> 00:05:09.990 align:start position:0% +aparecer aqui do ladinho o novo volume +que<00:05:07.840> seria<00:05:08.229> o<00:05:08.380> correspondente<00:05:08.770> ao<00:05:09.280> hd + +00:05:09.990 --> 00:05:10.000 align:start position:0% +que seria o correspondente ao hd + + +00:05:10.000 --> 00:05:14.730 align:start position:0% +que seria o correspondente ao hd +lá<00:05:10.960> é<00:05:11.530> um<00:05:11.740> volume<00:05:12.010> de<00:05:12.160> 500<00:05:12.460> gigabytes<00:05:12.840> que<00:05:13.840> é<00:05:14.590> o + +00:05:14.730 --> 00:05:14.740 align:start position:0% +lá é um volume de 500 gigabytes que é o + + +00:05:14.740 --> 00:05:17.370 align:start position:0% +lá é um volume de 500 gigabytes que é o +hd<00:05:15.010> que<00:05:15.220> eu<00:05:15.280> tirei<00:05:15.520> do<00:05:15.790> notebook<00:05:16.110><00:05:17.110> um<00:05:17.200> adendo + +00:05:17.370 --> 00:05:17.380 align:start position:0% +hd que eu tirei do notebook só um adendo + + +00:05:17.380 --> 00:05:19.710 align:start position:0% +hd que eu tirei do notebook só um adendo +no<00:05:17.740> vídeo<00:05:18.310> na<00:05:18.610> parte<00:05:18.910> anterior<00:05:18.970> do<00:05:19.300> vídeo<00:05:19.600> eu + +00:05:19.710 --> 00:05:19.720 align:start position:0% +no vídeo na parte anterior do vídeo eu + + +00:05:19.720 --> 00:05:22.920 align:start position:0% +no vídeo na parte anterior do vídeo eu +falei<00:05:19.960> sobre<00:05:20.940> utilizar<00:05:21.940> também<00:05:22.120> o<00:05:22.360> hd<00:05:22.690> de + +00:05:22.920 --> 00:05:22.930 align:start position:0% +falei sobre utilizar também o hd de + + +00:05:22.930 --> 00:05:24.780 align:start position:0% +falei sobre utilizar também o hd de +computador<00:05:23.080> de<00:05:23.830> desktop + +00:05:24.780 --> 00:05:24.790 align:start position:0% +computador de desktop + + +00:05:24.790 --> 00:05:27.150 align:start position:0% +computador de desktop +você<00:05:25.060> pode<00:05:25.270> sim<00:05:25.750> mas<00:05:26.169> é<00:05:26.260> obviamente<00:05:26.500> a<00:05:26.770> case + +00:05:27.150 --> 00:05:27.160 align:start position:0% +você pode sim mas é obviamente a case + + +00:05:27.160 --> 00:05:28.469 align:start position:0% +você pode sim mas é obviamente a case +tem<00:05:27.280> que<00:05:27.370> ser<00:05:27.490> diferente<00:05:27.820> essa<00:05:28.000> coisa<00:05:28.330> que<00:05:28.419> eu + +00:05:28.469 --> 00:05:28.479 align:start position:0% +tem que ser diferente essa coisa que eu + + +00:05:28.479 --> 00:05:31.500 align:start position:0% +tem que ser diferente essa coisa que eu +comprei<00:05:28.780> feita<00:05:29.080> pra<00:05:29.290> hd<00:05:30.070> de<00:05:30.430> notebook<00:05:30.640> e<00:05:31.210> um<00:05:31.300> hd + +00:05:31.500 --> 00:05:31.510 align:start position:0% +comprei feita pra hd de notebook e um hd + + +00:05:31.510 --> 00:05:33.000 align:start position:0% +comprei feita pra hd de notebook e um hd +de<00:05:31.630> 2<00:05:31.870> 6<00:05:32.110> polegadas + +00:05:33.000 --> 00:05:33.010 align:start position:0% +de 2 6 polegadas + + +00:05:33.010 --> 00:05:35.580 align:start position:0% +de 2 6 polegadas +o<00:05:33.130> hd<00:05:33.580> de<00:05:33.669> computador<00:05:33.820> desktop<00:05:34.600> é<00:05:35.169> uma<00:05:35.440> queda + +00:05:35.580 --> 00:05:35.590 align:start position:0% +o hd de computador desktop é uma queda + + +00:05:35.590 --> 00:05:37.260 align:start position:0% +o hd de computador desktop é uma queda +de<00:05:35.710> 36<00:05:36.070> polegadas + +00:05:37.260 --> 00:05:37.270 align:start position:0% +de 36 polegadas + + +00:05:37.270 --> 00:05:39.390 align:start position:0% +de 36 polegadas +aqui<00:05:37.570> a<00:05:37.630> gente<00:05:37.660> tem<00:05:37.870> os<00:05:38.260> arquivos<00:05:38.680> é<00:05:38.979> de<00:05:39.310> uma + +00:05:39.390 --> 00:05:39.400 align:start position:0% +aqui a gente tem os arquivos é de uma + + +00:05:39.400 --> 00:05:40.830 align:start position:0% +aqui a gente tem os arquivos é de uma +instalação<00:05:39.610> do<00:05:39.940> ubuntu<00:05:40.000> não<00:05:40.390> me<00:05:40.539> engano<00:05:40.780> que + +00:05:40.830 --> 00:05:40.840 align:start position:0% +instalação do ubuntu não me engano que + + +00:05:40.840 --> 00:05:44.490 align:start position:0% +instalação do ubuntu não me engano que +eu<00:05:40.900> fiz<00:05:41.229> é<00:05:41.740> no<00:05:42.220> notebook<00:05:42.520> mais<00:05:43.240> antigo<00:05:43.690> e<00:05:44.229> se<00:05:44.410> eu + +00:05:44.490 --> 00:05:44.500 align:start position:0% +eu fiz é no notebook mais antigo e se eu + + +00:05:44.500 --> 00:05:46.350 align:start position:0% +eu fiz é no notebook mais antigo e se eu +quisesse<00:05:44.919> essa<00:05:45.130> por<00:05:45.280> exemplo<00:05:45.580> usar<00:05:45.789> que<00:05:45.940> os + +00:05:46.350 --> 00:05:46.360 align:start position:0% +quisesse essa por exemplo usar que os + + +00:05:46.360 --> 00:05:49.590 align:start position:0% +quisesse essa por exemplo usar que os +pessoais<00:05:47.050> dentro<00:05:47.740> de<00:05:47.830> home<00:05:48.220> pela<00:05:48.820> alva<00:05:49.210> o<00:05:49.330> que + +00:05:49.590 --> 00:05:49.600 align:start position:0% +pessoais dentro de home pela alva o que + + +00:05:49.600 --> 00:05:51.870 align:start position:0% +pessoais dentro de home pela alva o que +é<00:05:49.660> o<00:05:49.930> meu<00:05:50.200> usuário<00:05:50.680> naquele<00:05:51.070> computador<00:05:51.220> e + +00:05:51.870 --> 00:05:51.880 align:start position:0% +é o meu usuário naquele computador e + + +00:05:51.880 --> 00:05:54.180 align:start position:0% +é o meu usuário naquele computador e +aqui<00:05:52.060> tem<00:05:52.240> todos<00:05:52.570> os<00:05:52.630> downloads<00:05:53.260> documentos<00:05:54.130> e + +00:05:54.180 --> 00:05:54.190 align:start position:0% +aqui tem todos os downloads documentos e + + +00:05:54.190 --> 00:05:55.020 align:start position:0% +aqui tem todos os downloads documentos e +tudo<00:05:54.310> mais + +00:05:55.020 --> 00:05:55.030 align:start position:0% +tudo mais + + +00:05:55.030 --> 00:05:58.770 align:start position:0% +tudo mais +se<00:05:55.510> esse<00:05:55.690> hd<00:05:55.900> fosse<00:05:56.530> um<00:05:56.919> sistema<00:05:57.520> o<00:05:57.789> windows<00:05:57.850> é + +00:05:58.770 --> 00:05:58.780 align:start position:0% +se esse hd fosse um sistema o windows é + + +00:05:58.780 --> 00:06:01.379 align:start position:0% +se esse hd fosse um sistema o windows é +promete<00:05:59.770> ser<00:06:00.039> entraria<00:06:00.880> que<00:06:01.030> na<00:06:01.120> pasta + +00:06:01.379 --> 00:06:01.389 align:start position:0% +promete ser entraria que na pasta + + +00:06:01.389 --> 00:06:05.790 align:start position:0% +promete ser entraria que na pasta +usuários<00:06:02.100> e<00:06:03.100> se<00:06:03.310> fosse<00:06:03.729> de<00:06:04.090> um<00:06:04.180> é<00:06:04.600> o<00:06:04.690> sx<00:06:05.350> ficaria + +00:06:05.790 --> 00:06:05.800 align:start position:0% +usuários e se fosse de um é o sx ficaria + + +00:06:05.800 --> 00:06:07.379 align:start position:0% +usuários e se fosse de um é o sx ficaria +na<00:06:05.889> pasta<00:06:06.280> e<00:06:06.370> os<00:06:06.639> verdes + +00:06:07.379 --> 00:06:07.389 align:start position:0% +na pasta e os verdes + + +00:06:07.389 --> 00:06:10.290 align:start position:0% +na pasta e os verdes +mas<00:06:08.020> basicamente<00:06:08.289> é<00:06:08.740> esse<00:06:09.100> o<00:06:09.160> processo<00:06:09.430> agora + +00:06:10.290 --> 00:06:10.300 align:start position:0% +mas basicamente é esse o processo agora + + +00:06:10.300 --> 00:06:12.000 align:start position:0% +mas basicamente é esse o processo agora +que<00:06:10.479> eu<00:06:10.539> vou<00:06:10.600> fazer<00:06:10.780> aqui<00:06:11.020> depois<00:06:11.229> de<00:06:11.410> salvar + +00:06:12.000 --> 00:06:12.010 align:start position:0% +que eu vou fazer aqui depois de salvar + + +00:06:12.010 --> 00:06:14.010 align:start position:0% +que eu vou fazer aqui depois de salvar +os<00:06:12.160> meus<00:06:12.340> arquivos<00:06:12.520> é<00:06:13.240> fazer<00:06:13.570> uma<00:06:13.690> formatação + +00:06:14.010 --> 00:06:14.020 align:start position:0% +os meus arquivos é fazer uma formatação + + +00:06:14.020 --> 00:06:17.100 align:start position:0% +os meus arquivos é fazer uma formatação +um<00:06:14.590> pouco<00:06:14.919> mais<00:06:15.130> lenta<00:06:15.520> nesse<00:06:15.760> hd<00:06:16.060> é<00:06:16.570> uma + +00:06:17.100 --> 00:06:17.110 align:start position:0% +um pouco mais lenta nesse hd é uma + + +00:06:17.110 --> 00:06:19.350 align:start position:0% +um pouco mais lenta nesse hd é uma +formatação<00:06:17.440> que<00:06:17.740> impeça<00:06:18.280> que<00:06:18.370> um<00:06:18.550> seu<00:06:19.120> por + +00:06:19.350 --> 00:06:19.360 align:start position:0% +formatação que impeça que um seu por + + +00:06:19.360 --> 00:06:21.680 align:start position:0% +formatação que impeça que um seu por +exemplo<00:06:19.720> no<00:06:19.810> futuro<00:06:20.200> perder<00:06:20.500> essa<00:06:20.710> case<00:06:21.160> é + +00:06:21.680 --> 00:06:21.690 align:start position:0% +exemplo no futuro perder essa case é + + +00:06:21.690 --> 00:06:23.879 align:start position:0% +exemplo no futuro perder essa case é +impeça<00:06:22.690> que<00:06:22.780> alguma<00:06:23.020> pessoa<00:06:23.110> consiga + +00:06:23.879 --> 00:06:23.889 align:start position:0% +impeça que alguma pessoa consiga + + +00:06:23.889 --> 00:06:25.860 align:start position:0% +impeça que alguma pessoa consiga +recuperar<00:06:24.370> esses<00:06:24.669> dados<00:06:24.940> antigos<00:06:25.479> que<00:06:25.660> estão + +00:06:25.860 --> 00:06:25.870 align:start position:0% +recuperar esses dados antigos que estão + + +00:06:25.870 --> 00:06:26.550 align:start position:0% +recuperar esses dados antigos que estão +aqui + +00:06:26.550 --> 00:06:26.560 align:start position:0% +aqui + + +00:06:26.560 --> 00:06:30.330 align:start position:0% +aqui +e<00:06:26.860> daí<00:06:27.550> depois<00:06:27.820> disso<00:06:28.300> eu<00:06:28.750> vou<00:06:28.870> então<00:06:29.340> poder + +00:06:30.330 --> 00:06:30.340 align:start position:0% +e daí depois disso eu vou então poder + + +00:06:30.340 --> 00:06:33.330 align:start position:0% +e daí depois disso eu vou então poder +usar<00:06:30.850> tranquilamente<00:06:31.539> esse<00:06:32.350> hd<00:06:32.800> externo<00:06:33.220> para + +00:06:33.330 --> 00:06:33.340 align:start position:0% +usar tranquilamente esse hd externo para + + +00:06:33.340 --> 00:06:36.540 align:start position:0% +usar tranquilamente esse hd externo para +copiar<00:06:33.669> dados<00:06:33.700> tudo<00:06:34.570> mais<00:06:34.780> então<00:06:35.440> a<00:06:35.800> idéia<00:06:36.190> eu + +00:06:36.540 --> 00:06:36.550 align:start position:0% +copiar dados tudo mais então a idéia eu + + +00:06:36.550 --> 00:06:38.590 align:start position:0% +copiar dados tudo mais então a idéia eu +vou<00:06:36.909> fazer<00:06:37.300> essa<00:06:37.419> formatação<00:06:37.840> pra + +00:06:38.590 --> 00:06:38.600 align:start position:0% +vou fazer essa formatação pra + + +00:06:38.600 --> 00:06:41.680 align:start position:0% +vou fazer essa formatação pra +que<00:06:38.840> seja<00:06:39.670> irrecuperável<00:06:40.670><00:06:40.940> o<00:06:41.150> que<00:06:41.540> está + +00:06:41.680 --> 00:06:41.690 align:start position:0% +que seja irrecuperável né o que está + + +00:06:41.690 --> 00:06:44.200 align:start position:0% +que seja irrecuperável né o que está +aqui<00:06:41.930> em<00:06:42.260> termos<00:06:42.440> de<00:06:42.530> passado<00:06:43.070> dos<00:06:43.280> dados<00:06:43.670> e + +00:06:44.200 --> 00:06:44.210 align:start position:0% +aqui em termos de passado dos dados e + + +00:06:44.210 --> 00:06:47.200 align:start position:0% +aqui em termos de passado dos dados e +depois<00:06:44.750> eu<00:06:44.870> vou<00:06:45.070> utilizar<00:06:46.070> normalmente<00:06:46.190> o<00:06:46.730> o + +00:06:47.200 --> 00:06:47.210 align:start position:0% +depois eu vou utilizar normalmente o o + + +00:06:47.210 --> 00:06:47.830 align:start position:0% +depois eu vou utilizar normalmente o o +hd + +00:06:47.830 --> 00:06:47.840 align:start position:0% +hd + + +00:06:47.840 --> 00:06:49.570 align:start position:0% +hd +eu<00:06:48.050> vou<00:06:48.200> também<00:06:48.320> na<00:06:48.920> verdade<00:06:49.220> antes<00:06:49.400> de + +00:06:49.570 --> 00:06:49.580 align:start position:0% +eu vou também na verdade antes de + + +00:06:49.580 --> 00:06:52.090 align:start position:0% +eu vou também na verdade antes de +formatar<00:06:50.170> passar<00:06:51.170> por<00:06:51.410> um<00:06:51.470> programa<00:06:51.740> que + +00:06:52.090 --> 00:06:52.100 align:start position:0% +formatar passar por um programa que + + +00:06:52.100 --> 00:06:55.120 align:start position:0% +formatar passar por um programa que +detecta<00:06:52.580> blogs<00:06:53.240> verifica<00:06:53.690> a<00:06:53.750> saúde<00:06:54.290> do<00:06:54.800> hd + +00:06:55.120 --> 00:06:55.130 align:start position:0% +detecta blogs verifica a saúde do hd + + +00:06:55.130 --> 00:06:57.250 align:start position:0% +detecta blogs verifica a saúde do hd +para<00:06:55.640> eu<00:06:55.760> ter<00:06:55.880> uma<00:06:55.910> noção<00:06:56.150> de<00:06:56.360> o<00:06:56.420> quanto<00:06:56.900> eu + +00:06:57.250 --> 00:06:57.260 align:start position:0% +para eu ter uma noção de o quanto eu + + +00:06:57.260 --> 00:07:00.250 align:start position:0% +para eu ter uma noção de o quanto eu +consigo<00:06:57.530> confiar<00:06:57.890> nele<00:06:58.190> e<00:06:58.730><00:06:59.120> depois<00:06:59.600> eu<00:06:59.990> vou + +00:07:00.250 --> 00:07:00.260 align:start position:0% +consigo confiar nele e aí depois eu vou + + +00:07:00.260 --> 00:07:02.350 align:start position:0% +consigo confiar nele e aí depois eu vou +utilizá-lo<00:07:01.070> novamente<00:07:01.190> ele<00:07:01.940> não<00:07:02.000> vai<00:07:02.240> ser + +00:07:02.350 --> 00:07:02.360 align:start position:0% +utilizá-lo novamente ele não vai ser + + +00:07:02.360 --> 00:07:06.070 align:start position:0% +utilizá-lo novamente ele não vai ser +usado<00:07:02.810> para<00:07:02.960> guardar<00:07:03.680> coisas<00:07:03.920> que<00:07:04.870> que<00:07:05.870> eu<00:07:05.990> vou + +00:07:06.070 --> 00:07:06.080 align:start position:0% +usado para guardar coisas que que eu vou + + +00:07:06.080 --> 00:07:08.170 align:start position:0% +usado para guardar coisas que que eu vou +ter<00:07:06.290> como<00:07:06.440> única<00:07:06.830> cópia<00:07:07.220> desse<00:07:07.460> hd + +00:07:08.170 --> 00:07:08.180 align:start position:0% +ter como única cópia desse hd + + +00:07:08.180 --> 00:07:09.820 align:start position:0% +ter como única cópia desse hd +a<00:07:08.240> idéia<00:07:08.660> é<00:07:08.690> que<00:07:08.780> eu<00:07:08.870> tenha<00:07:08.990> mais<00:07:09.230> cópias<00:07:09.710> + +00:07:09.820 --> 00:07:09.830 align:start position:0% +a idéia é que eu tenha mais cópias né + + +00:07:09.830 --> 00:07:12.250 align:start position:0% +a idéia é que eu tenha mais cópias né +porque<00:07:10.550> seu<00:07:10.850> hd<00:07:11.060> falhar<00:07:11.480> como<00:07:11.690> ele<00:07:11.840> joga<00:07:11.930> do + +00:07:12.250 --> 00:07:12.260 align:start position:0% +porque seu hd falhar como ele joga do + + +00:07:12.260 --> 00:07:13.990 align:start position:0% +porque seu hd falhar como ele joga do +antigo<00:07:12.680> eu<00:07:13.160> não<00:07:13.280> vou<00:07:13.370> ter<00:07:13.490> problema<00:07:13.820> em<00:07:13.880> ter + +00:07:13.990 --> 00:07:14.000 align:start position:0% +antigo eu não vou ter problema em ter + + +00:07:14.000 --> 00:07:17.140 align:start position:0% +antigo eu não vou ter problema em ter +perdido<00:07:14.240> esses<00:07:14.570> dados<00:07:15.040> é<00:07:16.040> isso<00:07:16.430> espero<00:07:17.060> que + +00:07:17.140 --> 00:07:17.150 align:start position:0% +perdido esses dados é isso espero que + + +00:07:17.150 --> 00:07:19.150 align:start position:0% +perdido esses dados é isso espero que +tenham<00:07:17.360> gostado<00:07:17.390><00:07:17.900> qualquer<00:07:18.500> dúvida<00:07:18.920> pode + +00:07:19.150 --> 00:07:19.160 align:start position:0% +tenham gostado aí qualquer dúvida pode + + +00:07:19.160 --> 00:07:21.940 align:start position:0% +tenham gostado aí qualquer dúvida pode +deixar<00:07:19.430> nos<00:07:19.520> comentários + diff --git a/tests/data/youtube-auto-limpeza-nespresso.vtt b/tests/data/youtube-auto-limpeza-nespresso.vtt new file mode 100644 index 0000000..519d5e8 --- /dev/null +++ b/tests/data/youtube-auto-limpeza-nespresso.vtt @@ -0,0 +1,824 @@ +WEBVTT +Kind: captions +Language: pt + +00:00:01.160 --> 00:00:04.349 align:start position:0% + +temos<00:00:01.439> aqui<00:00:01.599> uma<00:00:01.839> cafeteira<00:00:02.399> Nespresso + +00:00:04.349 --> 00:00:04.359 align:start position:0% +temos aqui uma cafeteira Nespresso + + +00:00:04.359 --> 00:00:07.070 align:start position:0% +temos aqui uma cafeteira Nespresso +iní<00:00:05.359> que<00:00:05.640> não + +00:00:07.070 --> 00:00:07.080 align:start position:0% +iní que não + + +00:00:07.080 --> 00:00:11.509 align:start position:0% +iní que não +inicia<00:00:08.080> a<00:00:08.400> água<00:00:09.320> puxar<00:00:09.639> a + +00:00:11.509 --> 00:00:11.519 align:start position:0% +inicia a água puxar a + + +00:00:11.519 --> 00:00:14.910 align:start position:0% +inicia a água puxar a +água<00:00:12.519> ligar + +00:00:14.910 --> 00:00:14.920 align:start position:0% + + + +00:00:14.920 --> 00:00:17.990 align:start position:0% + +aqui<00:00:15.920> um<00:00:16.080> tempinho<00:00:16.400> para<00:00:16.680> esquentar<00:00:17.680> eu<00:00:17.800> acho + +00:00:17.990 --> 00:00:18.000 align:start position:0% +aqui um tempinho para esquentar eu acho + + +00:00:18.000 --> 00:00:20.429 align:start position:0% +aqui um tempinho para esquentar eu acho +que<00:00:18.199><00:00:18.359> acontecendo<00:00:19.119> com<00:00:19.400> essa<00:00:19.640> cafeteira + +00:00:20.429 --> 00:00:20.439 align:start position:0% +que tá acontecendo com essa cafeteira + + +00:00:20.439 --> 00:00:24.029 align:start position:0% +que tá acontecendo com essa cafeteira +aqui<00:00:21.439> ela<00:00:21.760> ficou<00:00:22.039> muito<00:00:22.279> tempo<00:00:22.640> parada<00:00:23.240> então + +00:00:24.029 --> 00:00:24.039 align:start position:0% +aqui ela ficou muito tempo parada então + + +00:00:24.039 --> 00:00:26.470 align:start position:0% +aqui ela ficou muito tempo parada então +a<00:00:24.240> água<00:00:24.840> que<00:00:25.080> tava<00:00:25.560> aqui + +00:00:26.470 --> 00:00:26.480 align:start position:0% +a água que tava aqui + + +00:00:26.480 --> 00:00:30.950 align:start position:0% +a água que tava aqui +embaixo<00:00:27.480> ela<00:00:28.279> acabou<00:00:28.960> secando<00:00:29.519> ficou<00:00:30.080> ar<00:00:30.720> na + +00:00:30.950 --> 00:00:30.960 align:start position:0% +embaixo ela acabou secando ficou ar na + + +00:00:30.960 --> 00:00:33.150 align:start position:0% +embaixo ela acabou secando ficou ar na +tubulação<00:00:31.599> e<00:00:31.840> quando<00:00:32.079> ela<00:00:32.279> tenta<00:00:32.599> Sugar<00:00:32.960> ela + +00:00:33.150 --> 00:00:33.160 align:start position:0% +tubulação e quando ela tenta Sugar ela + + +00:00:33.160 --> 00:00:35.869 align:start position:0% +tubulação e quando ela tenta Sugar ela +acha<00:00:33.399> que<00:00:34.000> o<00:00:34.440> reservatório<00:00:35.040><00:00:35.200> vazio<00:00:35.640> porque + +00:00:35.869 --> 00:00:35.879 align:start position:0% +acha que o reservatório tá vazio porque + + +00:00:35.879 --> 00:00:37.110 align:start position:0% +acha que o reservatório tá vazio porque +só<00:00:36.040> tem<00:00:36.280> ar<00:00:36.480> na + +00:00:37.110 --> 00:00:37.120 align:start position:0% +só tem ar na + + +00:00:37.120 --> 00:00:40.750 align:start position:0% +só tem ar na +tubulação<00:00:38.120> E<00:00:38.320><00:00:38.559> ela<00:00:38.920> acaba<00:00:39.920> não<00:00:40.160> conseguindo + +00:00:40.750 --> 00:00:40.760 align:start position:0% +tubulação E aí ela acaba não conseguindo + + +00:00:40.760 --> 00:00:42.830 align:start position:0% +tubulação E aí ela acaba não conseguindo +puxar<00:00:41.239> acho<00:00:41.640> que<00:00:41.960> é<00:00:42.120> isso<00:00:42.320> que<00:00:42.399> aconteceu + +00:00:42.830 --> 00:00:42.840 align:start position:0% +puxar acho que é isso que aconteceu + + +00:00:42.840 --> 00:00:46.189 align:start position:0% +puxar acho que é isso que aconteceu +Vamos<00:00:43.039> tentar<00:00:43.280> aqui<00:00:44.239> vou<00:00:44.640> tirar + +00:00:46.189 --> 00:00:46.199 align:start position:0% +Vamos tentar aqui vou tirar + + +00:00:46.199 --> 00:00:49.470 align:start position:0% +Vamos tentar aqui vou tirar +aqui<00:00:47.559> bandejinha<00:00:48.559> chegar<00:00:48.920><00:00:49.079> pra<00:00:49.239> frente + +00:00:49.470 --> 00:00:49.480 align:start position:0% +aqui bandejinha chegar lá pra frente + + +00:00:49.480 --> 00:00:52.430 align:start position:0% +aqui bandejinha chegar lá pra frente +para<00:00:49.719> poder<00:00:49.920> cair<00:00:50.120> na<00:00:50.239> pia<00:00:51.199> vamos + +00:00:52.430 --> 00:00:52.440 align:start position:0% +para poder cair na pia vamos + + +00:00:52.440 --> 00:00:58.910 align:start position:0% +para poder cair na pia vamos +lá<00:00:53.440> não<00:00:54.039> tem + +00:00:58.910 --> 00:00:58.920 align:start position:0% + + + +00:00:58.920 --> 00:01:03.830 align:start position:0% + +cápsula + +00:01:03.830 --> 00:01:03.840 align:start position:0% + + + +00:01:03.840 --> 00:01:09.469 align:start position:0% + +não<00:01:04.000> sai<00:01:04.400> uma<00:01:04.519> gota<00:01:04.720> de + +00:01:09.469 --> 00:01:09.479 align:start position:0% + + + +00:01:09.479 --> 00:01:14.109 align:start position:0% + +ar<00:01:10.479> e<00:01:10.840> aqui<00:01:11.119> também<00:01:11.439> não<00:01:11.640> puxa + +00:01:14.109 --> 00:01:14.119 align:start position:0% +ar e aqui também não puxa + + +00:01:14.119 --> 00:01:18.590 align:start position:0% +ar e aqui também não puxa +nada<00:01:15.240> Vamos<00:01:16.240> colocar<00:01:16.560> aqui<00:01:16.720> no<00:01:17.240> modo<00:01:18.240> para + +00:01:18.590 --> 00:01:18.600 align:start position:0% +nada Vamos colocar aqui no modo para + + +00:01:18.600 --> 00:01:20.990 align:start position:0% +nada Vamos colocar aqui no modo para +tentar<00:01:18.920> fazer<00:01:19.200> com<00:01:19.360> que<00:01:19.520> ela<00:01:20.079> passe<00:01:20.560> toda<00:01:20.840> a + +00:01:20.990 --> 00:01:21.000 align:start position:0% +tentar fazer com que ela passe toda a + + +00:01:21.000 --> 00:01:23.510 align:start position:0% +tentar fazer com que ela passe toda a +água<00:01:21.320> de<00:01:21.439> uma<00:01:21.600> vez<00:01:21.840><00:01:22.680> por<00:01:22.880> ali<00:01:23.119> tem<00:01:23.439> que + +00:01:23.510 --> 00:01:23.520 align:start position:0% +água de uma vez só por ali tem que + + +00:01:23.520 --> 00:01:25.469 align:start position:0% +água de uma vez só por ali tem que +segurar<00:01:23.920> os<00:01:24.079> dois<00:01:24.240> botões<00:01:24.560> por<00:01:24.680> 5 + +00:01:25.469 --> 00:01:25.479 align:start position:0% +segurar os dois botões por 5 + + +00:01:25.479 --> 00:01:28.830 align:start position:0% +segurar os dois botões por 5 +segundos<00:01:26.479> até<00:01:26.840> eles + +00:01:28.830 --> 00:01:28.840 align:start position:0% +segundos até eles + + +00:01:28.840 --> 00:01:31.350 align:start position:0% +segundos até eles +piscarem + +00:01:31.350 --> 00:01:31.360 align:start position:0% +piscarem + + +00:01:31.360 --> 00:01:34.469 align:start position:0% +piscarem +começou<00:01:31.720> a<00:01:31.840> piscar<00:01:32.720> rápido<00:01:33.720> não<00:01:34.000> é<00:01:34.159> aquela + +00:01:34.469 --> 00:01:34.479 align:start position:0% +começou a piscar rápido não é aquela + + +00:01:34.479 --> 00:01:36.149 align:start position:0% +começou a piscar rápido não é aquela +piscada<00:01:34.840> de<00:01:34.960> quando<00:01:35.200> ela<00:01:35.399> liga<00:01:35.640> que<00:01:35.840> ela<00:01:36.000> + +00:01:36.149 --> 00:01:36.159 align:start position:0% +piscada de quando ela liga que ela tá + + +00:01:36.159 --> 00:01:38.749 align:start position:0% +piscada de quando ela liga que ela tá +aquecendo<00:01:37.159> aperta<00:01:37.479> qualquer<00:01:37.960> um<00:01:38.079> dos + +00:01:38.749 --> 00:01:38.759 align:start position:0% +aquecendo aperta qualquer um dos + + +00:01:38.759 --> 00:01:41.749 align:start position:0% +aquecendo aperta qualquer um dos +dois<00:01:39.759> ela<00:01:39.960> vai<00:01:40.119> começar<00:01:40.439> a<00:01:40.520> trabalhar<00:01:41.320> até<00:01:41.680> que + +00:01:41.749 --> 00:01:41.759 align:start position:0% +dois ela vai começar a trabalhar até que + + +00:01:41.759 --> 00:01:43.709 align:start position:0% +dois ela vai começar a trabalhar até que +toda<00:01:42.000> essa<00:01:42.200> água + +00:01:43.709 --> 00:01:43.719 align:start position:0% +toda essa água + + +00:01:43.719 --> 00:01:58.029 align:start position:0% +toda essa água +seja<00:01:44.719> passe<00:01:45.119> pelo<00:01:45.479> sistema<00:01:45.880> entra<00:01:46.280> no + +00:01:58.029 --> 00:01:58.039 align:start position:0% + + + +00:01:58.039 --> 00:02:01.789 align:start position:0% + +dela<00:01:59.039> ainda<00:01:59.479> assim + +00:02:01.789 --> 00:02:01.799 align:start position:0% +dela ainda assim + + +00:02:01.799 --> 00:02:05.230 align:start position:0% +dela ainda assim +nada<00:02:02.799> parece<00:02:03.119> realmente<00:02:03.640> ser<00:02:03.880> problema<00:02:04.159> de<00:02:04.240> ar + +00:02:05.230 --> 00:02:05.240 align:start position:0% +nada parece realmente ser problema de ar + + +00:02:05.240 --> 00:02:08.430 align:start position:0% +nada parece realmente ser problema de ar +ali<00:02:05.680> dentro<00:02:06.680> e<00:02:07.000> a<00:02:07.159> solução<00:02:07.479> que<00:02:07.600> eu<00:02:07.840> vi<00:02:08.239> que + +00:02:08.430 --> 00:02:08.440 align:start position:0% +ali dentro e a solução que eu vi que + + +00:02:08.440 --> 00:02:11.589 align:start position:0% +ali dentro e a solução que eu vi que +algumas<00:02:08.800> pessoas<00:02:09.239> fizeram<00:02:10.239> foi<00:02:10.720> injetar<00:02:11.280> água + +00:02:11.589 --> 00:02:11.599 align:start position:0% +algumas pessoas fizeram foi injetar água + + +00:02:11.599 --> 00:02:13.390 align:start position:0% +algumas pessoas fizeram foi injetar água +aqui<00:02:11.720> com<00:02:12.080> uma<00:02:12.280> seringa<00:02:12.760> para<00:02:12.920> poder<00:02:13.160> entrar + +00:02:13.390 --> 00:02:13.400 align:start position:0% +aqui com uma seringa para poder entrar + + +00:02:13.400 --> 00:02:16.990 align:start position:0% +aqui com uma seringa para poder entrar +com<00:02:13.720> pressão<00:02:14.720> E<00:02:15.040><00:02:15.200> com<00:02:15.360> isso<00:02:15.560> ela<00:02:16.000> consegue + +00:02:16.990 --> 00:02:17.000 align:start position:0% +com pressão E aí com isso ela consegue + + +00:02:17.000 --> 00:02:19.910 align:start position:0% +com pressão E aí com isso ela consegue +passar<00:02:17.400> pela<00:02:17.720> camada<00:02:18.040> de<00:02:18.160> ar<00:02:19.040> e<00:02:19.360> a<00:02:19.519> máquina + +00:02:19.910 --> 00:02:19.920 align:start position:0% +passar pela camada de ar e a máquina + + +00:02:19.920 --> 00:02:22.910 align:start position:0% +passar pela camada de ar e a máquina +consegue<00:02:20.640> continuar<00:02:20.840> o<00:02:21.319> ciclo<00:02:22.319> como<00:02:22.599> eu<00:02:22.800> não + +00:02:22.910 --> 00:02:22.920 align:start position:0% +consegue continuar o ciclo como eu não + + +00:02:22.920 --> 00:02:27.750 align:start position:0% +consegue continuar o ciclo como eu não +tenho<00:02:23.160> uma<00:02:23.280> seringa<00:02:24.160> aqui<00:02:25.160> eu<00:02:25.360> vou<00:02:25.560> tentar + +00:02:27.750 --> 00:02:27.760 align:start position:0% +tenho uma seringa aqui eu vou tentar + + +00:02:27.760 --> 00:02:30.509 align:start position:0% +tenho uma seringa aqui eu vou tentar +simplesmente<00:02:28.760> ter<00:02:29.040> certeza<00:02:29.440> de<00:02:29.840> que<00:02:30.080> eu<00:02:30.200> tenho + +00:02:30.509 --> 00:02:30.519 align:start position:0% +simplesmente ter certeza de que eu tenho + + +00:02:30.519 --> 00:02:32.630 align:start position:0% +simplesmente ter certeza de que eu tenho +água<00:02:31.080> aqui<00:02:31.360> nessa + +00:02:32.630 --> 00:02:32.640 align:start position:0% +água aqui nessa + + +00:02:32.640 --> 00:02:39.270 align:start position:0% +água aqui nessa +parte<00:02:33.640> colocar<00:02:33.920> um<00:02:34.040> pouquinho<00:02:34.360> aqui<00:02:34.480> com<00:02:34.599> a + +00:02:39.270 --> 00:02:39.280 align:start position:0% + + + +00:02:39.280 --> 00:02:42.270 align:start position:0% + +leiteira<00:02:40.280> ter<00:02:40.599> certeza<00:02:41.040> de<00:02:41.159> que<00:02:41.440> tem<00:02:41.800> água + +00:02:42.270 --> 00:02:42.280 align:start position:0% +leiteira ter certeza de que tem água + + +00:02:42.280 --> 00:02:44.949 align:start position:0% +leiteira ter certeza de que tem água +chegando<00:02:42.640> ali<00:02:43.200> e<00:02:43.680> daí<00:02:43.879> eu<00:02:44.000> vou<00:02:44.159> apertar<00:02:44.440> o + +00:02:44.949 --> 00:02:44.959 align:start position:0% +chegando ali e daí eu vou apertar o + + +00:02:44.959 --> 00:02:48.309 align:start position:0% +chegando ali e daí eu vou apertar o +botão<00:02:45.959> para<00:02:46.360> ela<00:02:46.560> começar<00:02:46.879> a<00:02:47.000> puxar<00:02:47.519> e<00:02:47.959> vou + +00:02:48.309 --> 00:02:48.319 align:start position:0% +botão para ela começar a puxar e vou + + +00:02:48.319 --> 00:02:50.710 align:start position:0% +botão para ela começar a puxar e vou +tentar<00:02:48.560> dar<00:02:48.720> umas<00:02:48.959> apertadas<00:02:49.519> aqui<00:02:50.280> para<00:02:50.599> ver + +00:02:50.710 --> 00:02:50.720 align:start position:0% +tentar dar umas apertadas aqui para ver + + +00:02:50.720 --> 00:02:52.790 align:start position:0% +tentar dar umas apertadas aqui para ver +se<00:02:50.879> ela<00:02:51.080> consegue<00:02:51.480> a<00:02:51.840> água<00:02:52.200> consegue<00:02:52.519> entrar + +00:02:52.790 --> 00:02:52.800 align:start position:0% +se ela consegue a água consegue entrar + + +00:02:52.800 --> 00:02:58.750 align:start position:0% +se ela consegue a água consegue entrar +ali<00:02:53.000> no + +00:02:58.750 --> 00:02:58.760 align:start position:0% + + + +00:02:58.760 --> 00:03:01.070 align:start position:0% + +circuito + +00:03:01.070 --> 00:03:01.080 align:start position:0% +circuito + + +00:03:01.080 --> 00:03:04.390 align:start position:0% +circuito +puxou<00:03:02.080> puxou<00:03:02.400> a<00:03:02.560> água<00:03:02.959> ainda<00:03:03.280> não<00:03:03.400> saiu<00:03:03.760> por + +00:03:04.390 --> 00:03:04.400 align:start position:0% +puxou puxou a água ainda não saiu por + + +00:03:04.400 --> 00:03:07.190 align:start position:0% +puxou puxou a água ainda não saiu por +aqui<00:03:05.400> mas<00:03:05.760> agora<00:03:06.040> eu<00:03:06.200> acredito + +00:03:07.190 --> 00:03:07.200 align:start position:0% +aqui mas agora eu acredito + + +00:03:07.200 --> 00:03:10.949 align:start position:0% +aqui mas agora eu acredito +que<00:03:08.200> colocando<00:03:08.879> aqui<00:03:09.879> ela<00:03:10.040> consegu<00:03:10.760> continuar + +00:03:10.949 --> 00:03:10.959 align:start position:0% +que colocando aqui ela consegu continuar + + +00:03:10.959 --> 00:03:13.390 align:start position:0% +que colocando aqui ela consegu continuar +o + +00:03:13.390 --> 00:03:13.400 align:start position:0% + + + +00:03:13.400 --> 00:03:17.309 align:start position:0% + +processo<00:03:14.400><00:03:15.120> + +00:03:17.309 --> 00:03:17.319 align:start position:0% +processo tá lá + + +00:03:17.319 --> 00:03:19.390 align:start position:0% +processo tá lá +funcionando<00:03:18.319> agora<00:03:18.720> é<00:03:18.879><00:03:19.040> fazer<00:03:19.239> uma + +00:03:19.390 --> 00:03:19.400 align:start position:0% +funcionando agora é só fazer uma + + +00:03:19.400 --> 00:03:20.309 align:start position:0% +funcionando agora é só fazer uma +limpezinha + +00:03:20.309 --> 00:03:20.319 align:start position:0% +limpezinha + + +00:03:20.319 --> 00:03:23.509 align:start position:0% +limpezinha +nela<00:03:21.319> e<00:03:21.879> voltar<00:03:22.159> a<00:03:22.319> tomar<00:03:22.480> os + +00:03:23.509 --> 00:03:23.519 align:start position:0% +nela e voltar a tomar os + + +00:03:23.519 --> 00:03:27.430 align:start position:0% +nela e voltar a tomar os +cafés<00:03:24.519> processo<00:03:25.000> de<00:03:25.200> limpeza<00:03:25.959> aqui<00:03:26.959> desse + +00:03:27.430 --> 00:03:27.440 align:start position:0% +cafés processo de limpeza aqui desse + + +00:03:27.440 --> 00:03:29.869 align:start position:0% +cafés processo de limpeza aqui desse +bico<00:03:28.080> das<00:03:28.439> cafeteiras<00:03:29.000> Nespresso<00:03:29.480> que<00:03:29.720> às + +00:03:29.869 --> 00:03:29.879 align:start position:0% +bico das cafeteiras Nespresso que às + + +00:03:29.879 --> 00:03:32.509 align:start position:0% +bico das cafeteiras Nespresso que às +vezes<00:03:30.120> pode<00:03:30.799> acumular<00:03:31.400> aqui<00:03:31.959> um<00:03:32.159> pouco<00:03:32.360> de + +00:03:32.509 --> 00:03:32.519 align:start position:0% +vezes pode acumular aqui um pouco de + + +00:03:32.519 --> 00:03:36.110 align:start position:0% +vezes pode acumular aqui um pouco de +borra<00:03:33.000> de<00:03:33.120> café<00:03:33.760> e<00:03:34.000><00:03:34.319> o<00:03:34.560> fluxo<00:03:35.040> sai<00:03:35.319> Menor<00:03:35.760> ela + +00:03:36.110 --> 00:03:36.120 align:start position:0% +borra de café e aí o fluxo sai Menor ela + + +00:03:36.120 --> 00:03:39.270 align:start position:0% +borra de café e aí o fluxo sai Menor ela +acaba<00:03:36.480> vazando<00:03:37.040> mais<00:03:37.239> aqui<00:03:37.840> para<00:03:38.159> dentro<00:03:39.159> a + +00:03:39.270 --> 00:03:39.280 align:start position:0% +acaba vazando mais aqui para dentro a + + +00:03:39.280 --> 00:03:42.190 align:start position:0% +acaba vazando mais aqui para dentro a +água<00:03:39.640> da<00:03:39.840> bandeja<00:03:40.319> enche<00:03:40.680> rápido<00:03:41.680> e<00:03:41.959> nem + +00:03:42.190 --> 00:03:42.200 align:start position:0% +água da bandeja enche rápido e nem + + +00:03:42.200 --> 00:03:44.750 align:start position:0% +água da bandeja enche rápido e nem +sempre<00:03:42.959><00:03:43.200> passar<00:03:43.599> água<00:03:44.000> por<00:03:44.080> ela<00:03:44.400> sem<00:03:44.640> a + +00:03:44.750 --> 00:03:44.760 align:start position:0% +sempre só passar água por ela sem a + + +00:03:44.760 --> 00:03:47.229 align:start position:0% +sempre só passar água por ela sem a +cápsula<00:03:45.280> resolve<00:03:46.000> tem<00:03:46.519> muita<00:03:46.720> coisa<00:03:46.920> que<00:03:47.000> fica + +00:03:47.229 --> 00:03:47.239 align:start position:0% +cápsula resolve tem muita coisa que fica + + +00:03:47.239 --> 00:03:49.550 align:start position:0% +cápsula resolve tem muita coisa que fica +impregnada<00:03:47.840> aqui<00:03:48.200> uma<00:03:48.760> maneira<00:03:49.080> que<00:03:49.159> eu<00:03:49.319> vi + +00:03:49.550 --> 00:03:49.560 align:start position:0% +impregnada aqui uma maneira que eu vi + + +00:03:49.560 --> 00:03:52.429 align:start position:0% +impregnada aqui uma maneira que eu vi +que<00:03:49.760> eu<00:03:49.879> inclusive<00:03:50.519> achei<00:03:50.920> no<00:03:51.080> YouTube<00:03:51.360> é<00:03:52.360> o + +00:03:52.429 --> 00:03:52.439 align:start position:0% +que eu inclusive achei no YouTube é o + + +00:03:52.439 --> 00:03:54.949 align:start position:0% +que eu inclusive achei no YouTube é o +seguinte<00:03:52.920> Tira<00:03:53.200> aqui<00:03:53.480> o<00:03:53.840> reservatório<00:03:54.680> Tira + +00:03:54.949 --> 00:03:54.959 align:start position:0% +seguinte Tira aqui o reservatório Tira + + +00:03:54.959 --> 00:03:56.630 align:start position:0% +seguinte Tira aqui o reservatório Tira +toda<00:03:55.200> a<00:03:55.280> parte<00:03:55.480> aqui<00:03:55.720> não<00:03:55.840> tem<00:03:56.040> cápsula<00:03:56.519> aqui + +00:03:56.630 --> 00:03:56.640 align:start position:0% +toda a parte aqui não tem cápsula aqui + + +00:03:56.640 --> 00:03:59.990 align:start position:0% +toda a parte aqui não tem cápsula aqui +dentro<00:03:56.840> deixa<00:03:57.079> ela<00:03:57.400> aberta<00:03:58.400> você<00:03:58.720> vai<00:03:59.760> virar + +00:03:59.990 --> 00:04:00.000 align:start position:0% +dentro deixa ela aberta você vai virar + + +00:04:00.000 --> 00:04:01.509 align:start position:0% +dentro deixa ela aberta você vai virar +ela<00:04:00.200> de<00:04:00.400> cabeça<00:04:00.720> para + +00:04:01.509 --> 00:04:01.519 align:start position:0% +ela de cabeça para + + +00:04:01.519 --> 00:04:04.069 align:start position:0% +ela de cabeça para +baixo<00:04:02.519> se<00:04:02.599> eu<00:04:02.680> consigo<00:04:03.000> mostrar<00:04:03.280> aqui + +00:04:04.069 --> 00:04:04.079 align:start position:0% +baixo se eu consigo mostrar aqui + + +00:04:04.079 --> 00:04:07.030 align:start position:0% +baixo se eu consigo mostrar aqui +inclinar<00:04:05.040> para<00:04:05.319> a<00:04:05.480> frente<00:04:06.239> ou<00:04:06.439> seja<00:04:06.720> não<00:04:06.879> é + +00:04:07.030 --> 00:04:07.040 align:start position:0% +inclinar para a frente ou seja não é + + +00:04:07.040 --> 00:04:08.949 align:start position:0% +inclinar para a frente ou seja não é +para<00:04:07.239> ela<00:04:07.400> ficar<00:04:07.799> na<00:04:07.959> posição<00:04:08.400> totalmente + +00:04:08.949 --> 00:04:08.959 align:start position:0% +para ela ficar na posição totalmente + + +00:04:08.959 --> 00:04:13.470 align:start position:0% +para ela ficar na posição totalmente +aqui<00:04:09.480> na<00:04:09.959> é<00:04:10.879> 90º<00:04:11.879> para<00:04:12.560> uma<00:04:12.760> leve<00:04:13.040> inclinação + +00:04:13.470 --> 00:04:13.480 align:start position:0% +aqui na é 90º para uma leve inclinação + + +00:04:13.480 --> 00:04:16.150 align:start position:0% +aqui na é 90º para uma leve inclinação +na<00:04:13.680> frente<00:04:14.000> por<00:04:14.200> qu<00:04:14.840> a<00:04:15.040> gente<00:04:15.200> vai<00:04:15.319> jogar<00:04:15.760> água + +00:04:16.150 --> 00:04:16.160 align:start position:0% +na frente por qu a gente vai jogar água + + +00:04:16.160 --> 00:04:18.629 align:start position:0% +na frente por qu a gente vai jogar água +com<00:04:16.359> a<00:04:16.519> torneira<00:04:17.359> se<00:04:17.680> possível<00:04:18.160> água<00:04:18.440> quente + +00:04:18.629 --> 00:04:18.639 align:start position:0% +com a torneira se possível água quente + + +00:04:18.639 --> 00:04:20.390 align:start position:0% +com a torneira se possível água quente +com<00:04:18.840> a<00:04:18.959> maior<00:04:19.199> pressão<00:04:19.519> possível<00:04:20.000> aqui<00:04:20.239> no + +00:04:20.390 --> 00:04:20.400 align:start position:0% +com a maior pressão possível aqui no + + +00:04:20.400 --> 00:04:23.749 align:start position:0% +com a maior pressão possível aqui no +bico<00:04:21.280> E<00:04:21.560><00:04:22.040> a<00:04:22.280> água<00:04:22.639> vai<00:04:22.880> passar<00:04:23.160> por<00:04:23.320> dentro + +00:04:23.749 --> 00:04:23.759 align:start position:0% +bico E aí a água vai passar por dentro + + +00:04:23.759 --> 00:04:26.189 align:start position:0% +bico E aí a água vai passar por dentro +do<00:04:23.880> bico<00:04:24.160> e<00:04:24.360> sair<00:04:25.040> ali<00:04:25.320> por<00:04:25.440> cima + +00:04:26.189 --> 00:04:26.199 align:start position:0% +do bico e sair ali por cima + + +00:04:26.199 --> 00:04:28.909 align:start position:0% +do bico e sair ali por cima +é<00:04:27.199> é<00:04:27.360> importante<00:04:27.800> inclinar<00:04:28.400> justamente<00:04:28.720> para + +00:04:28.909 --> 00:04:28.919 align:start position:0% +é é importante inclinar justamente para + + +00:04:28.919 --> 00:04:30.350 align:start position:0% +é é importante inclinar justamente para +que<00:04:29.039> a<00:04:29.160> água<00:04:29.440> não + +00:04:30.350 --> 00:04:30.360 align:start position:0% +que a água não + + +00:04:30.360 --> 00:04:33.310 align:start position:0% +que a água não +entre<00:04:31.360> na<00:04:31.520> máquina<00:04:31.880> para<00:04:32.039> que<00:04:32.160> ela<00:04:32.320> caia<00:04:32.840> ali + +00:04:33.310 --> 00:04:33.320 align:start position:0% +entre na máquina para que ela caia ali + + +00:04:33.320 --> 00:04:38.390 align:start position:0% +entre na máquina para que ela caia ali +na<00:04:33.800> pia<00:04:34.800> então<00:04:35.199><00:04:35.560> com<00:04:35.720> a<00:04:35.800> máquina<00:04:36.080> aqui<00:04:36.880> vou + +00:04:38.390 --> 00:04:38.400 align:start position:0% +na pia então tô com a máquina aqui vou + + +00:04:38.400 --> 00:04:40.670 align:start position:0% +na pia então tô com a máquina aqui vou +inclinar<00:04:39.400> água + +00:04:40.670 --> 00:04:40.680 align:start position:0% +inclinar água + + +00:04:40.680 --> 00:04:58.550 align:start position:0% +inclinar água +quente<00:04:41.680> na<00:04:42.000> maior<00:04:42.240> pressão + +00:04:58.550 --> 00:04:58.560 align:start position:0% + + + +00:04:58.560 --> 00:05:11.469 align:start position:0% + +possível + +00:05:11.469 --> 00:05:11.479 align:start position:0% + + + +00:05:11.479 --> 00:05:14.550 align:start position:0% + +esperar<00:05:11.880> escorrer + +00:05:14.550 --> 00:05:14.560 align:start position:0% + + + +00:05:14.560 --> 00:05:17.629 align:start position:0% + +tudo<00:05:15.560> e<00:05:15.800> Daí<00:05:16.080> podemos<00:05:16.440> virar<00:05:16.759> novamente<00:05:17.320> a + +00:05:17.629 --> 00:05:17.639 align:start position:0% +tudo e Daí podemos virar novamente a + + +00:05:17.639 --> 00:05:19.950 align:start position:0% +tudo e Daí podemos virar novamente a +nossa<00:05:17.919> máquina<00:05:18.759> esse<00:05:19.160> procedimento<00:05:19.720> também + +00:05:19.950 --> 00:05:19.960 align:start position:0% +nossa máquina esse procedimento também + + +00:05:19.960 --> 00:05:21.990 align:start position:0% +nossa máquina esse procedimento também +funciona<00:05:20.280> com<00:05:20.440> outras<00:05:20.759> máquinas<00:05:21.199> Nespresso + +00:05:21.990 --> 00:05:22.000 align:start position:0% +funciona com outras máquinas Nespresso + + +00:05:22.000 --> 00:05:24.350 align:start position:0% +funciona com outras máquinas Nespresso +com<00:05:22.120> esse<00:05:22.319> tipo<00:05:22.479> de<00:05:22.639> cápsula<00:05:23.639> e<00:05:23.800> fica<00:05:24.039> aqui<00:05:24.240> a + +00:05:24.350 --> 00:05:24.360 align:start position:0% +com esse tipo de cápsula e fica aqui a + + +00:05:24.360 --> 00:05:26.430 align:start position:0% +com esse tipo de cápsula e fica aqui a +dica<00:05:24.759> para<00:05:24.960> quem<00:05:25.160> quiser<00:05:25.400> comprar<00:05:25.680> cápsulas + +00:05:26.430 --> 00:05:26.440 align:start position:0% +dica para quem quiser comprar cápsulas + + +00:05:26.440 --> 00:05:29.870 align:start position:0% +dica para quem quiser comprar cápsulas +de<00:05:26.600> café<00:05:27.080> especial<00:05:28.000> microlotes<00:05:29.000> em<00:05:29.560> vários + +00:05:29.870 --> 00:05:29.880 align:start position:0% +de café especial microlotes em vários + + +00:05:29.880 --> 00:05:32.830 align:start position:0% +de café especial microlotes em vários +tipos<00:05:30.560> entra<00:05:30.919> aqui<00:05:31.080> no<00:05:31.240> site<00:05:31.440> do<00:05:31.720> crio<00:05:32.240> café + +00:05:32.830 --> 00:05:32.840 align:start position:0% +tipos entra aqui no site do crio café + + +00:05:32.840 --> 00:05:34.830 align:start position:0% +tipos entra aqui no site do crio café +que<00:05:32.960> é<00:05:33.080> uma<00:05:33.280> cafeteria<00:05:33.840> torrefação<00:05:34.560> de<00:05:34.680> São + +00:05:34.830 --> 00:05:34.840 align:start position:0% +que é uma cafeteria torrefação de São + + +00:05:34.840 --> 00:05:36.909 align:start position:0% +que é uma cafeteria torrefação de São +Paulo<00:05:35.160> mas<00:05:35.280> que<00:05:35.520> entrega<00:05:35.880> para<00:05:36.039> todo<00:05:36.240> o<00:05:36.360> Brasil + +00:05:36.909 --> 00:05:36.919 align:start position:0% +Paulo mas que entrega para todo o Brasil + + +00:05:36.919 --> 00:05:39.189 align:start position:0% +Paulo mas que entrega para todo o Brasil +e<00:05:37.160> tem<00:05:37.520> inclusive<00:05:37.960> assinatura<00:05:38.720> café<00:05:39.000> com + +00:05:39.189 --> 00:05:39.199 align:start position:0% +e tem inclusive assinatura café com + + +00:05:39.199 --> 00:05:41.950 align:start position:0% +e tem inclusive assinatura café com +cápsula<00:05:39.720> café<00:05:39.919> em<00:05:40.080> grão<00:05:40.400> moído<00:05:41.280> e<00:05:41.479> um<00:05:41.639> monte<00:05:41.880> de + +00:05:41.950 --> 00:05:41.960 align:start position:0% +cápsula café em grão moído e um monte de + + +00:05:41.960 --> 00:05:46.280 align:start position:0% +cápsula café em grão moído e um monte de +outras<00:05:42.240> coisas<00:05:42.560> gostosas<00:05:43.280> valeu + diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..1ead546 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,65 @@ +import io +import re +from pathlib import Path + +import webvtt + +from youtool import utils + + +TEST_DATA_DIR = Path(__file__).parent / "data" + +def extract_vtt_words(content): + vtt = webvtt.read_buffer(io.StringIO(content)) + words = " ".join(caption.text for caption in vtt.captions) + return re.sub("[[:punct:]]+", "", words).lower().split() + + +def test_simplify_vtt(): + # Each transcription has 3 versions: + # - whisper-clean: whisper transcription of the audio file without word timings (similar to "simplified" version) + # - whisper-words: whisper transcription of the audio file with word timings (more verbose) + # - youtube-auto: automatic transcription downloaded from YouTube, with lots of garbage + filenames = [ + TEST_DATA_DIR / "whisper-clean-hd-notebook.vtt", + TEST_DATA_DIR / "whisper-words-hd-notebook.vtt", + TEST_DATA_DIR / "youtube-auto-hd-notebook.vtt", + ] + content = [] + for filename in filenames: + with open(filename) as fobj: + content.append(fobj.read()) + # Check if VTTs are different (some have duplication, word timings etc.) + assert content[0] != content[1] + assert content[1] != content[2] + # Check if all whisper simplified versions are the same (timings will be the same) + assert utils.simplify_vtt(content[0]) == utils.simplify_vtt(content[1]) + # Check if simplified version of whisper with no word timings has more or less the same number of words of + # simplified YouTube version + whisper_str = extract_vtt_words(utils.simplify_vtt(content[0])) + youtube_str = extract_vtt_words(utils.simplify_vtt(content[2])) + # Number of words (not unique) will be more or less the same if simplification worked - if not, difference will be + # huge (`youtube_words` would be higher). + assert 0.9 <= len(whisper_words) / len(youtube_words) <= 1.1 + + filenames = [ + TEST_DATA_DIR / "whisper-clean-limpeza-nespresso.vtt", + TEST_DATA_DIR / "whisper-words-limpeza-nespresso.vtt", + TEST_DATA_DIR / "youtube-auto-limpeza-nespresso.vtt", + ] + content = [] + for filename in filenames: + with open(filename) as fobj: + content.append(fobj.read()) + # Check if VTTs are different (some have duplication, word timings etc.) + assert content[0] != content[1] + assert content[1] != content[2] + # Check if all whisper simplified versions are the same (timings will be the same) + assert utils.simplify_vtt(content[0]) == utils.simplify_vtt(content[1]) + # Check if simplified version of whisper with no word timings has more or less the same number of words of + # simplified YouTube version + whisper_str = extract_vtt_words(utils.simplify_vtt(content[0])) + youtube_str = extract_vtt_words(utils.simplify_vtt(content[2])) + # Number of words (not unique) will be more or less the same if simplification worked - if not, difference will be + # huge (`youtube_words` would be higher). + assert 0.9 <= len(whisper_words) / len(youtube_words) <= 1.1 diff --git a/youtool/utils.py b/youtool/utils.py new file mode 100644 index 0000000..0ecd338 --- /dev/null +++ b/youtool/utils.py @@ -0,0 +1,41 @@ +import io + + +def vtt_to_string(vtt): + result = io.StringIO() + vtt.write(result) + result.seek(0) + return result.read() + + +def simplify_vtt(vtt): + """Simplify VTT contents, removing per-word timings and deduplicating sentences + + `vtt` can be either `str` or `webvtt.WebVTT` instance + """ + import webvtt # noqa + + if isinstance(vtt, str): + vtt = webvtt.read_buffer(io.StringIO(vtt)) + elif not isinstance(vtt, webvtt.WebVTT): + raise TypeError(f"`vtt` must be instance of either `str` or `webvtt.WebVTT` (got: {type(vtt)})") + simplified = [] + last_line = None + for caption in vtt.captions: + lines = caption.text.strip().splitlines() + for line in lines: + if not line: + continue + elif line == last_line: + if simplified: + simplified[-1].end = caption.end + continue + simplified.append(webvtt.Caption(start=caption.start, end=caption.end, text=line)) + last_line = line + # TODO: fix logic to have consecutive timings when simplifing YouTube automatic transcriptions - from + # `tests/data/youtube-auto-hd-notebook.vtt`: + # 00:07:14.000 00:07:19.150 perdido esses dados é isso espero que + # 00:07:17.150 00:07:21.940 tenham gostado aí qualquer dúvida pode + # 00:07:19.160 00:07:21.940 deixar nos comentários + new_vtt = webvtt.WebVTT(captions=simplified) + return vtt_to_string(new_vtt) From 5e54bd4b713a70a79bbfc8ca21074f7e3f44ff6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Wed, 10 Jul 2024 01:26:40 -0300 Subject: [PATCH 07/85] Fix parsing emojis (livechat) --- youtool/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/youtool/__init__.py b/youtool/__init__.py index f60780c..829d4a3 100644 --- a/youtool/__init__.py +++ b/youtool/__init__.py @@ -517,8 +517,10 @@ def video_livechat(self, video_id: str, expand_emojis=True): text = message["message"] if expand_emojis: for emoji in message.get("emotes", []): - for shortcut in emoji["shortcuts"]: - text = text.replace(shortcut, emoji["id"]) + shortcuts = emoji.get("shortcuts") + if shortcuts: + for shortcut in shortcuts: + text = text.replace(shortcut, emoji["id"]) money = message.get("money", {}) or {} yield { "id": message["message_id"], From 7b96b277a70817a0c6d1efd5d523285034301cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sat, 13 Jul 2024 23:35:31 -0300 Subject: [PATCH 08/85] Lint and fix test variables --- tests/test_utils.py | 10 +++++----- youtool/__init__.py | 34 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 1ead546..8c67502 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,9 +6,9 @@ from youtool import utils - TEST_DATA_DIR = Path(__file__).parent / "data" + def extract_vtt_words(content): vtt = webvtt.read_buffer(io.StringIO(content)) words = " ".join(caption.text for caption in vtt.captions) @@ -36,8 +36,8 @@ def test_simplify_vtt(): assert utils.simplify_vtt(content[0]) == utils.simplify_vtt(content[1]) # Check if simplified version of whisper with no word timings has more or less the same number of words of # simplified YouTube version - whisper_str = extract_vtt_words(utils.simplify_vtt(content[0])) - youtube_str = extract_vtt_words(utils.simplify_vtt(content[2])) + whisper_words = extract_vtt_words(utils.simplify_vtt(content[0])) + youtube_words = extract_vtt_words(utils.simplify_vtt(content[2])) # Number of words (not unique) will be more or less the same if simplification worked - if not, difference will be # huge (`youtube_words` would be higher). assert 0.9 <= len(whisper_words) / len(youtube_words) <= 1.1 @@ -58,8 +58,8 @@ def test_simplify_vtt(): assert utils.simplify_vtt(content[0]) == utils.simplify_vtt(content[1]) # Check if simplified version of whisper with no word timings has more or less the same number of words of # simplified YouTube version - whisper_str = extract_vtt_words(utils.simplify_vtt(content[0])) - youtube_str = extract_vtt_words(utils.simplify_vtt(content[2])) + whisper_words = extract_vtt_words(utils.simplify_vtt(content[0])) + youtube_words = extract_vtt_words(utils.simplify_vtt(content[2])) # Number of words (not unique) will be more or less the same if simplification worked - if not, difference will be # huge (`youtube_words` would be higher). assert 0.9 <= len(whisper_words) / len(youtube_words) <= 1.1 diff --git a/youtool/__init__.py b/youtool/__init__.py index 829d4a3..12a95f5 100644 --- a/youtool/__init__.py +++ b/youtool/__init__.py @@ -582,27 +582,27 @@ def videos_transcriptions(self, videos_ids, language_code, path, skip_downloaded def video_search( self, - term: str=None, - region_code: str=None, - language_code: str=None, - since: datetime.datetime=None, - until: datetime.datetime=None, - order: str="date", - channel_id: str=None, + term: str = None, + region_code: str = None, + language_code: str = None, + since: datetime.datetime = None, + until: datetime.datetime = None, + order: str = "date", + channel_id: str = None, channel_type=None, event_type=None, topic=None, video_type=None, - location: tuple=None, - location_radius: str=None, - safe_search: str=None, - video_caption: str=None, - video_definition: str=None, - video_dimension: str=None, - video_embeddable: str=None, - video_paid_product_placement: str=None, - video_syndicated: str=None, - video_license: str=None, + location: tuple = None, + location_radius: str = None, + safe_search: str = None, + video_caption: str = None, + video_definition: str = None, + video_dimension: str = None, + video_embeddable: str = None, + video_paid_product_placement: str = None, + video_syndicated: str = None, + video_license: str = None, video_category_id=None, ): """Get list of videos from a search (not all video parameters will be filled, check `parse_video_data`) From dec2cacf3b868cd9ef21083bbb69438a06383ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sat, 13 Jul 2024 23:35:55 -0300 Subject: [PATCH 09/85] Change version to 0.1.2 --- setup.cfg | 2 +- youtool/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b8d3b2d..3a0d9f3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = youtool -version = 0.1.1 +version = 0.1.2 description = Easy-to-use library to access YouTube Data API v3 in bulk operations long_description = file: README.md long_description_content_type = text/markdown diff --git a/youtool/__init__.py b/youtool/__init__.py index 12a95f5..3c51e8c 100644 --- a/youtool/__init__.py +++ b/youtool/__init__.py @@ -1,3 +1,4 @@ +__version__ = "0.1.2" import datetime import re from collections import defaultdict From e4b2d41996554e57e85df15f04c8e5a1cee155f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 14 Jul 2024 02:37:03 -0300 Subject: [PATCH 10/85] _get_ydl now can set yt-dlp to download media --- README.md | 19 +++++++++++-------- requirements/base.txt | 1 - requirements/cli.txt | 1 - requirements/livechat.txt | 1 - youtool/__init__.py | 39 ++++++++++++++++++++++++++++++++++----- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 67cb322..20cd67e 100644 --- a/README.md +++ b/README.md @@ -120,16 +120,19 @@ download_path = Path("transcriptions") if not download_path.exists(): download_path.mkdir(parents=True) print(f"Downloading Portuguese (pt) transcriptions for videos {video_id} and {live_video_id} - saving at {download_path.absolute()}") -yt.videos_transcriptions([video_id, live_video_id], language_code="pt", path=download_path) -for vid in [video_id, live_video_id]: - result = list(download_path.glob(f"{vid}*vtt")) - if not result: - print(f" Transcription for video {vid} could not be downloaded.") - else: - filename = result[0] - print(f" Downloaded: {filename} ({filename.stat().st_size / 1024:.1f} KiB)") +for downloaded in yt.download_transcriptions([video_id, live_video_id], language_code="pt", path=download_path): + vid, status, filename = downloaded["video_id"], downloaded["status"], downloaded["filename"] + if status == "error": + print(f" {vid}: error downloading!") + elif status == "skipped": + print(f" {vid}: skipped, file already exists ({filename}: {filename.stat().st_size / 1024:.1f} KiB)") + elif status == "done": + print(f" {vid}: done ({filename}: {filename.stat().st_size / 1024:.1f} KiB)") print("-" * 80) +# You can also download audio and video, just replace `download_transcriptions` with `download_audios` or +# `download_videos`. As simple as it is. :) + print("Categories in Brazilian YouTube:") for category in yt.categories(region_code="BR"): # `category` is a `dict` diff --git a/requirements/base.txt b/requirements/base.txt index 3581af3..ea93b32 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,3 +1,2 @@ isodate requests - diff --git a/requirements/cli.txt b/requirements/cli.txt index 6216202..0ff8c5c 100644 --- a/requirements/cli.txt +++ b/requirements/cli.txt @@ -1,3 +1,2 @@ loguru tqdm - diff --git a/requirements/livechat.txt b/requirements/livechat.txt index 5f940c9..f035dbe 100644 --- a/requirements/livechat.txt +++ b/requirements/livechat.txt @@ -1,2 +1 @@ chat-downloader - diff --git a/youtool/__init__.py b/youtool/__init__.py index 3c51e8c..c1fbad1 100644 --- a/youtool/__init__.py +++ b/youtool/__init__.py @@ -16,6 +16,20 @@ REGEXP_NAIVE_DATETIME = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}$") REGEXP_DATETIME_MILLIS = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+") +def _file_search(path, filename_search_pattern, video_id, language_code, media_format): + filenames = list( + path.glob( + filename_search_pattern.format( + video_id=video_id, + language_code=language_code, + media_format=media_format, + ) + ) + ) + if not filenames: + return None + return filenames[0] + def cleanup(data): """Remove NUL (\x00) from str, dict e lists, recursively @@ -538,20 +552,35 @@ def video_livechat(self, video_id: str, expand_emojis=True): "money_amount": parse_decimal(money.get("amount")), } - def _get_ydl(self, path_pattern, language_code): + def _get_ydl(self, path_pattern, language_code=None, media_format=None): + """Create an instance of `YouTubeDL` and caches it + + `media_format` can be "bestaudio", "bestvideo" or any format ID returned by `yt-dlp -f ` command + `language_code` can be any from the list returned by `yt-dlp --list-subs ` command + `language_code` implies skipping download of the media, so can't be used with `media_format` + """ import yt_dlp - key = (language_code, str(path_pattern)) + if language_code is not None and media_format is not None: + raise ValueError("`language_code` implies skipping download of media, so `media_format` cannot be used") + key = (str(path_pattern), language_code, media_format) if key not in self._ydls: options = { "cachedir": False, "noprogress": True, "outtmpl": str(path_pattern), "quiet": True, - "skip_download": True, - "subtitleslangs": [language_code], - "writeautomaticsub": True, } + if language_code is not None: + options.update( + { + "skip_download": True, + "subtitleslangs": [language_code], + "writeautomaticsub": True, + } + ) + elif media_format is not None: + options.update({"format": media_format}) if self.disable_ipv6: options["source_address"] = "0.0.0.0" self._ydls[key] = yt_dlp.YoutubeDL(options) From 491eae849e5dd403ba622ab2460d82583baa32ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 14 Jul 2024 02:39:46 -0300 Subject: [PATCH 11/85] Implement new yt-dlp download pipeline Now it's iterable and yield download statuses --- youtool/__init__.py | 81 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/youtool/__init__.py b/youtool/__init__.py index c1fbad1..75634fd 100644 --- a/youtool/__init__.py +++ b/youtool/__init__.py @@ -587,28 +587,81 @@ def _get_ydl(self, path_pattern, language_code=None, media_format=None): return self._ydls[key] def videos_transcriptions(self, videos_ids, language_code, path, skip_downloaded=True, batch_size=10): - """Download video transcriptions (automatically generated) using yt-dlp""" - # TODO: add a callback for stats? - language_code = language_code.lower() - path_pattern = Path(path).absolute() / "%(id)s" - ydl = self._get_ydl(path_pattern, language_code) - batch = [] + """DEPRECATED: use `download_transcriptions` instead (you must iterate over it so it executes)""" + return list( + self.download_transcriptions( + videos_ids=videos_ids, + language_code=language_code, + path=path, + skip_downloaded=skip_downloaded, + batch_size=batch_size, + ) + ) + + def download_transcriptions(self, videos_ids, language_code, path, skip_downloaded=True, batch_size=10): + """Download videos in best available format using yt-dlp, usually saves as `.mp4` and returns final status""" + yield from self._process_ytdlp_batches( + videos_ids=videos_ids, + path=path, + language_code=language_code, + skip_downloaded=skip_downloaded, + batch_size=batch_size, + filename_search_pattern="{video_id}.{language_code}.vtt", + ) + + def _process_ytdlp_batches(self, videos_ids, path, language_code=None, media_format=None, skip_downloaded=True, + batch_size=10, filename_pattern="%(id)s.%(ext)s", filename_search_pattern=None): + if filename_search_pattern is None: + filename_search_pattern = "{video_id}.*" + path = Path(path) + path_pattern = path.absolute() / filename_pattern + ydl = self._get_ydl(path_pattern=path_pattern, media_format=media_format, language_code=language_code) + total = len(videos_ids) if hasattr(videos_ids, "__len__") else None + statuses, filenames = {}, {} + batch, executed = [], [] for video_id in videos_ids: - filename = str(path_pattern).replace("%(id)s", f"{video_id}.{language_code}.vtt") - if skip_downloaded and Path(filename).exists(): - continue - batch.append(f"https://www.youtube.com/watch?v={video_id}") + executed.append(video_id) + filename = _file_search(path, filename_search_pattern, video_id, language_code, media_format) + if skip_downloaded and filename: + statuses[video_id] = "skipped" + filenames[video_id] = filename + exception = None + else: + batch.append(f"https://www.youtube.com/watch?v={video_id}") if len(batch) == batch_size: + exception = None try: ydl.download(batch) - except Exception: - pass + except Exception as exp: + exception = exp + for video_id in executed: + filename = _file_search(path, filename_search_pattern, video_id, language_code, media_format) + if filename: + filenames[video_id] = filename + if statuses.get(video_id) is None: # Could be 'skipped' + statuses[video_id] = "done" + else: + statuses[video_id] = "error" + for key in executed: + yield {"video_id": key, "status": statuses[key], "filename": filenames.get(key)} batch = [] + executed = [] if batch: + exception = None try: ydl.download(batch) - except Exception: - pass + except Exception as exp: + exception = exp + for video_id in executed: + filename = _file_search(path, filename_search_pattern, video_id, language_code, media_format) + if filename: + filenames[video_id] = filename + if statuses.get(video_id) is None: # Could be 'skipped' + statuses[video_id] = "done" + else: + statuses[video_id] = "error" + for key in executed: + yield {"video_id": key, "status": statuses[key], "filename": filenames.get(key)} def video_search( self, From df43cccfd90164a0b32884ceddf2e3bfe28c4279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 14 Jul 2024 02:40:23 -0300 Subject: [PATCH 12/85] Implement `download_{audios,videos,media}` methods --- youtool/__init__.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/youtool/__init__.py b/youtool/__init__.py index 75634fd..e5926c7 100644 --- a/youtool/__init__.py +++ b/youtool/__init__.py @@ -609,6 +609,40 @@ def download_transcriptions(self, videos_ids, language_code, path, skip_download filename_search_pattern="{video_id}.{language_code}.vtt", ) + def download_audios(self, videos_ids, path, skip_downloaded=True, batch_size=10): + """Download audios in best available format using yt-dlp, usually saves as `.m4a` and returns final status""" + yield from self.download_media( + videos_ids=videos_ids, + path=path, + media_format="bestaudio", + skip_downloaded=skip_downloaded, + batch_size=batch_size, + ) + + def download_videos(self, videos_ids, path, skip_downloaded=True, batch_size=10): + """Download videos in best available format using yt-dlp, usually saves as `.mp4` and returns final status""" + yield from self.download_media( + videos_ids=videos_ids, + path=path, + media_format="bestvideo", + skip_downloaded=skip_downloaded, + batch_size=batch_size, + ) + + def download_media(self, videos_ids, media_format, path, skip_downloaded=True, batch_size=10): + """Download video media (video only, audio only or both) using yt-dlp and returns final status per video + + `media_format` can be "bestaudio", "bestvideo" or any format ID returned by `yt-dlp -f ` command + """ + yield from self._process_ytdlp_batches( + videos_ids=videos_ids, + path=path, + media_format=media_format, + skip_downloaded=skip_downloaded, + batch_size=batch_size, + filename_search_pattern="{video_id}.*", + ) + def _process_ytdlp_batches(self, videos_ids, path, language_code=None, media_format=None, skip_downloaded=True, batch_size=10, filename_pattern="%(id)s.%(ext)s", filename_search_pattern=None): if filename_search_pattern is None: From 22ec9433176f4462010d3303111d897bc094242f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 14 Jul 2024 02:41:30 -0300 Subject: [PATCH 13/85] Change version to 0.2.0 --- setup.cfg | 2 +- youtool/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 3a0d9f3..77478cb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = youtool -version = 0.1.2 +version = 0.2.0 description = Easy-to-use library to access YouTube Data API v3 in bulk operations long_description = file: README.md long_description_content_type = text/markdown diff --git a/youtool/__init__.py b/youtool/__init__.py index e5926c7..7db2836 100644 --- a/youtool/__init__.py +++ b/youtool/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.2" +__version__ = "0.2.0" import datetime import re from collections import defaultdict From a2cad7217f98bdd80ec99862333629fc9c9d5fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 14 Jul 2024 02:52:54 -0300 Subject: [PATCH 14/85] Move transcription test to use new method + lint --- tests/test_YouTube.py | 9 +++++---- youtool/__init__.py | 26 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/test_YouTube.py b/tests/test_YouTube.py index 977edb4..4d9d1b4 100644 --- a/tests/test_YouTube.py +++ b/tests/test_YouTube.py @@ -193,7 +193,7 @@ def test_YouTube_video_livechat(): @pytest.mark.usefixtures("tmpdir") -def test_YouTube_videos_transcriptions(): +def test_YouTube_download_transcriptions(): lang = "pt" filenames = [TMP_FILE_PATH / f"{video_id}.{lang}.vtt" for video_id in vtt_videos_ids] # Make sure files do not exist before downloading @@ -201,9 +201,10 @@ def test_YouTube_videos_transcriptions(): if filename.exists(): filename.unlink() - yt.videos_transcriptions(vtt_videos_ids, lang, TMP_FILE_PATH) - for filename in filenames: - assert filename.exists() + for status in yt.download_transcriptions(vtt_videos_ids, lang, TMP_FILE_PATH): + assert status[ + "filename" + ].exists(), f"Cannot download transcriptions for {status['video_id']} (status: {status['status']})" def test_YouTube_video_search(): diff --git a/youtool/__init__.py b/youtool/__init__.py index 7db2836..28bbe83 100644 --- a/youtool/__init__.py +++ b/youtool/__init__.py @@ -16,6 +16,7 @@ REGEXP_NAIVE_DATETIME = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}$") REGEXP_DATETIME_MILLIS = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+") + def _file_search(path, filename_search_pattern, video_id, language_code, media_format): filenames = list( path.glob( @@ -643,14 +644,22 @@ def download_media(self, videos_ids, media_format, path, skip_downloaded=True, b filename_search_pattern="{video_id}.*", ) - def _process_ytdlp_batches(self, videos_ids, path, language_code=None, media_format=None, skip_downloaded=True, - batch_size=10, filename_pattern="%(id)s.%(ext)s", filename_search_pattern=None): + def _process_ytdlp_batches( + self, + videos_ids, + path, + language_code=None, + media_format=None, + skip_downloaded=True, + batch_size=10, + filename_pattern="%(id)s.%(ext)s", + filename_search_pattern=None, + ): if filename_search_pattern is None: filename_search_pattern = "{video_id}.*" path = Path(path) path_pattern = path.absolute() / filename_pattern ydl = self._get_ydl(path_pattern=path_pattern, media_format=media_format, language_code=language_code) - total = len(videos_ids) if hasattr(videos_ids, "__len__") else None statuses, filenames = {}, {} batch, executed = [], [] for video_id in videos_ids: @@ -659,15 +668,13 @@ def _process_ytdlp_batches(self, videos_ids, path, language_code=None, media_for if skip_downloaded and filename: statuses[video_id] = "skipped" filenames[video_id] = filename - exception = None else: batch.append(f"https://www.youtube.com/watch?v={video_id}") if len(batch) == batch_size: - exception = None try: ydl.download(batch) - except Exception as exp: - exception = exp + except Exception: + pass for video_id in executed: filename = _file_search(path, filename_search_pattern, video_id, language_code, media_format) if filename: @@ -681,11 +688,10 @@ def _process_ytdlp_batches(self, videos_ids, path, language_code=None, media_for batch = [] executed = [] if batch: - exception = None try: ydl.download(batch) - except Exception as exp: - exception = exp + except Exception: + pass for video_id in executed: filename = _file_search(path, filename_search_pattern, video_id, language_code, media_format) if filename: From 2ecbda4b1bc9064058c6e2189a2f3d6b71a2ed1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 19 May 2024 21:26:02 -0300 Subject: [PATCH 15/85] Add console_scripts to setup.cfg --- setup.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.cfg b/setup.cfg index 77478cb..2cffba5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,10 @@ packages = find: python_requires = >=3.7 install_requires = file: requirements/base.txt +[options.entry_points] +console_scripts = + youtool = youtool:cli + [options.extras_require] cli = file: requirements/cli.txt dev = file: requirements/dev.txt From 252ff46e14cc221b07bda07843aa94934e9d6162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 19 May 2024 21:28:04 -0300 Subject: [PATCH 16/85] Implement draft CLI module --- youtool/cli.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 youtool/cli.py diff --git a/youtool/cli.py b/youtool/cli.py new file mode 100644 index 0000000..be0bbd0 --- /dev/null +++ b/youtool/cli.py @@ -0,0 +1,44 @@ +import argparse + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--api-key") + subparsers = parser.add_subparsers(required=True, dest="command") + + api_key = args.api_key or os.environ.get("YOUTUBE_API_KEY") + + cmd_channel_id = subparsers.add_parser("channel-id", help="Get channel IDs from a list of URLs (or CSV filename with URLs inside), generate CSV output (just the IDs)") + cmd_channel_info = subparsers.add_parser("channel-info", help="Get channel info from a list of IDs (or CSV filename with IDs inside), generate CSV output (same schema for `channel` dicts)") + cmd_video_info = subparsers.add_parser("video-info", help="Get video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (same schema for `video` dicts)") + cmd_video_search = subparsers.add_parser("video-search", help="Get video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (simplified `video` dict schema or option to get full video info after)") + cmd_video_comments = subparsers.add_parser("video-comments", help="Get comments from a video ID, generate CSV output (same schema for `comment` dicts)") + cmd_video_livechat = subparsers.add_parser("video-livechat", help="Get comments from a video ID, generate CSV output (same schema for `chat_message` dicts)") + cmd_video_transcriptions = subparsers.add_parser("video-transcription", help="Download video transcriptions based on language code, path and list of video IDs or URLs (or CSV filename with URLs/IDs inside), download files to destination and report results") + + args = parser.parse_args() + + if args.command == "channel-id": + print(f"Implement: {args.command}") # TODO: implement + + elif args.command == "channel-info": + print(f"Implement: {args.command}") # TODO: implement + + elif args.command == "video-info": + print(f"Implement: {args.command}") # TODO: implement + + elif args.command == "video-search": + print(f"Implement: {args.command}") # TODO: implement + + elif args.command == "video-comments": + print(f"Implement: {args.command}") # TODO: implement + + elif args.command == "video-livechat": + print(f"Implement: {args.command}") # TODO: implement + + elif args.command == "video-transcription": + print(f"Implement: {args.command}") # TODO: implement + + +if __name__ == "__main__": + main() From dcc9e2f16ed3db17258108292b63f71d93773f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sun, 19 May 2024 21:30:52 -0300 Subject: [PATCH 17/85] Add old/draft CLI search code --- youtool/cli.py | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/youtool/cli.py b/youtool/cli.py index be0bbd0..ff675a3 100644 --- a/youtool/cli.py +++ b/youtool/cli.py @@ -1,4 +1,6 @@ import argparse +import os +import sys def main(): @@ -29,6 +31,118 @@ def main(): elif args.command == "video-search": print(f"Implement: {args.command}") # TODO: implement + exit(1) + + # TODO: update code below based on new YouTube class API + import rows + from loguru import logger + from tqdm import tqdm + + from youtool import YouTube + + parser = argparse.ArgumentParser() + parser.add_argument("--key") + parser.add_argument("csv_filename") + parser.add_argument("url", nargs="+") + args = parser.parse_args() + + key = args.key or os.environ.get("YOUTUBE_API_KEY") + if not key: + print("ERROR: Must provide an API key (--key or YOUTUBE_API_KEY env var)", file=sys.stderr) + exit(1) + + if not Path(args.csv_filename).parent.exists(): + Path(args.csv_filename).parent.mkdir(parents=True) + writer = rows.utils.CsvLazyDictWriter(args.csv_filename) # TODO: use csv + yt = YouTube(key) + videos_urls = [] + channels = {} + for url in tqdm(args.url, desc="Retrieving channel IDs"): + url = url.strip() + if "/watch?" in url: + videos_urls.append(url) + continue + channel_id = yt.channel_id_from_url(url) + if not channel_id: + username = url.split("youtube.com/")[1].split("?")[0].split("/")[0] + logger.warning(f"Channel ID not found for URL {url}") + continue + channels[channel_id] = { + "id": channel_id, + "url": url, + } + for channel_id, playlist_id in yt.playlists_ids(list(channels.keys())).items(): + channels[channel_id]["playlist_id"] = playlist_id + fields = "id duration definition status views likes dislikes favorites comments channel_id title description published_at scheduled_to finished_at concurrent_viewers started_at".split() + # TODO: check fields + for data in tqdm(channels.values(), desc="Retrieving videos"): + try: + for video_batch in ipartition(yt.playlist_videos(data["playlist_id"]), 50): + for video in yt.videos_infos([row["id"] for row in video_batch]): + writer.writerow({field: video.get(field) for field in fields}) + except RuntimeError: # Cannot find playlist + continue + videos_ids = (video_url.split("watch?v=")[1].split("&")[0] for video_url in videos_urls) + for video in tqdm(yt.videos_infos(videos_ids), desc="Retrieving individual videos"): + writer.writerow({field: video.get(field) for field in fields}) + writer.close() + + # SEARCH + now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) + timezone_br = datetime.timezone(offset=datetime.timedelta(hours=-3)) + now_br = now.astimezone(timezone_br) + search_start = (now - datetime.timedelta(hours=1)).replace(minute=0, second=0, microsecond=0) + search_stop = search_start + datetime.timedelta(hours=1) + + parent = Path(__file__).parent + parser = argparse.ArgumentParser() + parser.add_argument("--keys-filename", default=parent / "youtube-keys.csv") + parser.add_argument("--terms-filename", default=parent / "search-terms.csv") + parser.add_argument("--channels-filename", default=parent / "search-channels.csv") + parser.add_argument("--start", default=str(search_start)) + parser.add_argument("--stop", default=str(search_stop)) + parser.add_argument("--limit", type=int, default=20) + parser.add_argument("--order", default="viewCount") + parser.add_argument("data_path") + args = parser.parse_args() + + data_path = Path(args.data_path) + keys_filename = Path(args.keys_filename) + terms_filename = Path(args.terms_filename) + channels_filename = Path(args.channels_filename) + now_path_name = now_br.strftime("%Y-%m-%dT%H") + youtube_keys = read_keys(keys_filename) + channels_groups = read_channels(args.channels_filename) + search_start, search_stop = args.start, args.stop + if isinstance(search_start, str): + search_start = datetime.datetime.fromisoformat(search_start) + if isinstance(search_stop, str): + search_stop = datetime.datetime.fromisoformat(search_stop) + search_start_str = search_start.astimezone(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + search_stop_str = search_stop.astimezone(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + search_limit = args.limit + search_order = args.order + terms_categories = read_csv_dictlist(terms_filename, "categoria", "termo") + + print(search_start_str) + print(search_stop_str) + + search_start_br = search_start.astimezone(timezone_br) + result_filename = data_path / f"search_{search_start_br.strftime('%Y-%m-%dT%H')}.csv" + writer = rows.utils.CsvLazyDictWriter(result_filename) + search_results = youtube_search( + terms_categories=terms_categories, + keys=youtube_keys["search"], + start=search_start_str, + stop=search_stop_str, + channels_groups=channels_groups, + order=search_order, + limit=search_limit, + ) + for result in search_results: + writer.writerow(result) + writer.close() + elif args.command == "video-comments": print(f"Implement: {args.command}") # TODO: implement From f2540a8784ebc7bd884d22f08840d806523e9022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Justen=20=28=40turicas=29?= Date: Sat, 8 Jun 2024 14:13:49 -0300 Subject: [PATCH 18/85] Add useful scripts (to be added to utils and CLI) --- scripts/channel_data.py | 187 ++++++++++++++++++++++++++++++++++++++++ scripts/clean_vtt.py | 43 +++++++++ 2 files changed, 230 insertions(+) create mode 100644 scripts/channel_data.py create mode 100644 scripts/clean_vtt.py diff --git a/scripts/channel_data.py b/scripts/channel_data.py new file mode 100644 index 0000000..e00b965 --- /dev/null +++ b/scripts/channel_data.py @@ -0,0 +1,187 @@ +# pip install youtool[livechat,transcription] +import argparse +import os +import json +import shelve +from pathlib import Path + +from chat_downloader.errors import ChatDisabled, LoginRequired, NoChatReplay +from tqdm import tqdm +from youtool import YouTube + + +class CsvLazyDictWriter: # Got and adapted from + """Lazy CSV dict writer, so you don't need to specify field names beforehand + + This class is almost the same as `csv.DictWriter` with the following + differences: + + - You don't need to pass `fieldnames` (it's extracted on the first + `.writerow` call); + - You can pass either a filename or a fobj (like `sys.stdout`); + """ + + def __init__(self, filename_or_fobj, encoding="utf-8", *args, **kwargs): + self.writer = None + self.filename_or_fobj = filename_or_fobj + self.encoding = encoding + self._fobj = None + self.writer_args = args + self.writer_kwargs = kwargs + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def fobj(self): + if self._fobj is None: + if getattr(self.filename_or_fobj, "read", None) is not None: + self._fobj = self.filename_or_fobj + else: + self._fobj = open( + self.filename_or_fobj, mode="w", encoding=self.encoding + ) + + return self._fobj + + def writerow(self, row): + if self.writer is None: + self.writer = csv.DictWriter( + self.fobj, + fieldnames=list(row.keys()), + *self.writer_args, + **self.writer_kwargs + ) + self.writer.writeheader() + + self.writerow = self.writer.writerow + return self.writerow(row) + + def __del__(self): + self.close() + + def close(self): + if self._fobj and not self._fobj.closed: + self._fobj.close() + + +# TODO: add options to get only part of the data (not all steps) +parser = argparse.ArgumentParser() +parser.add_argument("--api-key", default=os.environ.get("YOUTUBE_API_KEY"), help="Comma-separated list of YouTube API keys to use") +parser.add_argument("username_or_channel_url", type=str) +parser.add_argument("data_path", type=Path) +parser.add_argument("language-code", default="pt-orig", help="See the list by running `yt-dlp --list-subs `") +args = parser.parse_args() + +if not args.api_key: + import sys + + print("ERROR: API key must be provided either by `--api-key` or `YOUTUBE_API_KEY` environment variable", file=sys.stderr) + exit(1) +api_keys = [key.strip() for key in args.api_key.split(",") if key.strip()] + + +username = args.username +if username.startswith("https://"): + channel_url = username + username = [item for item in username.split("/") if item][-1] +else: + channel_url = f"https://www.youtube.com/@{username}" +data_path = args.data_path +channel_csv_filename = data_path / f"{username}-channel.csv" +playlist_csv_filename = data_path / f"{username}-playlist.csv" +playlist_video_csv_filename = data_path / f"{username}-playlist-video.csv" +video_csv_filename = data_path / f"{username}-video.csv" +comment_csv_filename = data_path / f"{username}-comment.csv" +livechat_csv_filename = data_path / f"username}-livechat.csv" +language_code = args.language_code +video_transcription_path = data_path / Path(f"{username}-transcriptions") + +yt = YouTube(api_keys, disable_ipv6=True) +video_transcription_path.mkdir(parents=True, exist_ok=True) +channel_writer = CsvLazyDictWriter(channel_csv_filename) +playlist_writer = CsvLazyDictWriter(playlist_csv_filename) +video_writer = CsvLazyDictWriter(video_csv_filename) +comment_writer = CsvLazyDictWriter(comment_csv_filename) +livechat_writer = CsvLazyDictWriter(livechat_csv_filename) +playlist_video_writer = CsvLazyDictWriter(playlist_video_csv_filename) + +print("Retrieving channel info") +channel_id = yt.channel_id_from_url(channel_url) +channel_info = list(yt.channels_infos([channel_id]))[0] +channel_writer.writerow(channel_info) +channel_writer.close() + +main_playlist = { + "id": channel_info["playlist_id"], + "title": "Uploads", + "description": channel_info["description"], + "videos": channel_info["videos"], + "channel_id": channel_id, + "channel_title": channel_info["title"], + "published_at": channel_info["published_at"], + "thumbnail_url": channel_info["thumbnail_url"], +} +playlist_writer.writerow(main_playlist) +playlist_ids = [channel_info["playlist_id"]] +for playlist in tqdm(yt.channel_playlists(channel_id), desc="Retrieving channel playlists"): + playlist_writer.writerow(playlist) + playlist_ids.append(playlist["id"]) +playlist_writer.close() + +video_ids = [] +for playlist_id in tqdm(playlist_ids, desc="Retrieving playlists' videos"): + for video in yt.playlist_videos(playlist_id): + if video["id"] not in video_ids: + video_ids.append(video["id"]) + row = { + "playlist_id": playlist_id, + "video_id": video["id"], + "video_status": video["status"], + "channel_id": video["channel_id"], + "channel_title": video["channel_title"], + "playlist_channel_id": video["playlist_channel_id"], + "playlist_channel_title": video["playlist_channel_title"], + "title": video["title"], + "description": video["description"], + "published_at": video["published_at"], + "added_to_playlist_at": video["added_to_playlist_at"], + "tags": video["tags"], + } + playlist_video_writer.writerow(row) +playlist_video_writer.close() + +videos = [] +for video in tqdm(yt.videos_infos(video_ids), desc="Retrieving detailed video information"): + videos.append(video) + video_writer.writerow(video) +video_writer.close() + +for video_id in tqdm(video_ids, desc="Retrieving video comments"): + try: + for comment in yt.video_comments(video_id): + comment_writer.writerow(comment) + except RuntimeError: # Comments disabled + continue +comment_writer.close() + +print("Retrieving transcriptions") +yt.videos_transcriptions( + video_ids, + language_code=language_code, + path=video_transcription_path, + skip_downloaded=True, + batch_size=10, +) + +# TODO: live chat code will freeze if it's not available +for video_id in tqdm(video_ids, desc="Retrieving live chat"): + try: + for comment in yt.video_livechat(video_id): + livechat_writer.writerow(comment) + except (LoginRequired, NoChatReplay, ChatDisabled): + continue +livechat_writer.close() diff --git a/scripts/clean_vtt.py b/scripts/clean_vtt.py new file mode 100644 index 0000000..3412b59 --- /dev/null +++ b/scripts/clean_vtt.py @@ -0,0 +1,43 @@ +# pip install webvtt-py +import argparse +import io +import json +import os +import shelve +import time +from pathlib import Path + +import tiktoken +import webvtt +from openai import APITimeoutError, OpenAI +from rows.utils import CsvLazyDictWriter +from tqdm import tqdm + + +def vtt_clean(vtt_content, same_line=False): + result_lines, last_line = [], None + for caption in webvtt.read_buffer(io.StringIO(vtt_content)): + new_lines = caption.text.strip().splitlines() + for line in new_lines: + line = line.strip() + if not line or line == last_line: + continue + result_lines.append(f"{str(caption.start).split('.')[0]} {line}\n" if not same_line else f"{line} ") + last_line = line + return "".join(result_lines) + + +parser = argparse.ArgumentParser() +parser.add_argument("input_path", type=Path) +parser.add_argument("output_path", type=Path) +args = parser.parse_args() + +for filename in tqdm(args.input_path.glob("*.vtt")): + new_filename = args.output_path / filename.name + if new_filename.exists(): + continue + with filename.open() as fobj: + data = fobj.read() + result = vtt_clean(data) + with new_filename.open(mode="w") as fobj: + fobj.write(result) From 079e5ee2d94a781583067680c0c3f82a49cdb062 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 25 Jun 2024 11:14:23 -0300 Subject: [PATCH 19/85] - Add argparse integration and command handling for Youtube CLI Tool --- youtool/cli.py | 179 ++++++++++--------------------------------------- 1 file changed, 37 insertions(+), 142 deletions(-) diff --git a/youtool/cli.py b/youtool/cli.py index ff675a3..6926185 100644 --- a/youtool/cli.py +++ b/youtool/cli.py @@ -1,158 +1,53 @@ import argparse import os -import sys +from commands import COMMANDS -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--api-key") - subparsers = parser.add_subparsers(required=True, dest="command") - - api_key = args.api_key or os.environ.get("YOUTUBE_API_KEY") - - cmd_channel_id = subparsers.add_parser("channel-id", help="Get channel IDs from a list of URLs (or CSV filename with URLs inside), generate CSV output (just the IDs)") - cmd_channel_info = subparsers.add_parser("channel-info", help="Get channel info from a list of IDs (or CSV filename with IDs inside), generate CSV output (same schema for `channel` dicts)") - cmd_video_info = subparsers.add_parser("video-info", help="Get video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (same schema for `video` dicts)") - cmd_video_search = subparsers.add_parser("video-search", help="Get video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (simplified `video` dict schema or option to get full video info after)") - cmd_video_comments = subparsers.add_parser("video-comments", help="Get comments from a video ID, generate CSV output (same schema for `comment` dicts)") - cmd_video_livechat = subparsers.add_parser("video-livechat", help="Get comments from a video ID, generate CSV output (same schema for `chat_message` dicts)") - cmd_video_transcriptions = subparsers.add_parser("video-transcription", help="Download video transcriptions based on language code, path and list of video IDs or URLs (or CSV filename with URLs/IDs inside), download files to destination and report results") - - args = parser.parse_args() - - if args.command == "channel-id": - print(f"Implement: {args.command}") # TODO: implement - - elif args.command == "channel-info": - print(f"Implement: {args.command}") # TODO: implement - - elif args.command == "video-info": - print(f"Implement: {args.command}") # TODO: implement - elif args.command == "video-search": - print(f"Implement: {args.command}") # TODO: implement - exit(1) - - # TODO: update code below based on new YouTube class API - import rows - from loguru import logger - from tqdm import tqdm - - from youtool import YouTube - - parser = argparse.ArgumentParser() - parser.add_argument("--key") - parser.add_argument("csv_filename") - parser.add_argument("url", nargs="+") - args = parser.parse_args() - - key = args.key or os.environ.get("YOUTUBE_API_KEY") - if not key: - print("ERROR: Must provide an API key (--key or YOUTUBE_API_KEY env var)", file=sys.stderr) - exit(1) - - if not Path(args.csv_filename).parent.exists(): - Path(args.csv_filename).parent.mkdir(parents=True) - writer = rows.utils.CsvLazyDictWriter(args.csv_filename) # TODO: use csv - yt = YouTube(key) - videos_urls = [] - channels = {} - for url in tqdm(args.url, desc="Retrieving channel IDs"): - url = url.strip() - if "/watch?" in url: - videos_urls.append(url) - continue - channel_id = yt.channel_id_from_url(url) - if not channel_id: - username = url.split("youtube.com/")[1].split("?")[0].split("/")[0] - logger.warning(f"Channel ID not found for URL {url}") - continue - channels[channel_id] = { - "id": channel_id, - "url": url, - } - for channel_id, playlist_id in yt.playlists_ids(list(channels.keys())).items(): - channels[channel_id]["playlist_id"] = playlist_id - fields = "id duration definition status views likes dislikes favorites comments channel_id title description published_at scheduled_to finished_at concurrent_viewers started_at".split() - # TODO: check fields - for data in tqdm(channels.values(), desc="Retrieving videos"): - try: - for video_batch in ipartition(yt.playlist_videos(data["playlist_id"]), 50): - for video in yt.videos_infos([row["id"] for row in video_batch]): - writer.writerow({field: video.get(field) for field in fields}) - except RuntimeError: # Cannot find playlist - continue - videos_ids = (video_url.split("watch?v=")[1].split("&")[0] for video_url in videos_urls) - for video in tqdm(yt.videos_infos(videos_ids), desc="Retrieving individual videos"): - writer.writerow({field: video.get(field) for field in fields}) - writer.close() - - # SEARCH - now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) - timezone_br = datetime.timezone(offset=datetime.timedelta(hours=-3)) - now_br = now.astimezone(timezone_br) - search_start = (now - datetime.timedelta(hours=1)).replace(minute=0, second=0, microsecond=0) - search_stop = search_start + datetime.timedelta(hours=1) +def main(): + """ + Main function for the YouTube CLI Tool. - parent = Path(__file__).parent - parser = argparse.ArgumentParser() - parser.add_argument("--keys-filename", default=parent / "youtube-keys.csv") - parser.add_argument("--terms-filename", default=parent / "search-terms.csv") - parser.add_argument("--channels-filename", default=parent / "search-channels.csv") - parser.add_argument("--start", default=str(search_start)) - parser.add_argument("--stop", default=str(search_stop)) - parser.add_argument("--limit", type=int, default=20) - parser.add_argument("--order", default="viewCount") - parser.add_argument("data_path") - args = parser.parse_args() + This function sets up the argument parser for the CLI tool, including options for the YouTube API key and + command-specific subparsers. It then parses the command-line arguments, retrieving the YouTube API key + from either the command-line argument '--api-key' or the environment variable 'YOUTUBE_API_KEY'. If the API + key is not provided through any means, it raises an argparse.ArgumentError. - data_path = Path(args.data_path) - keys_filename = Path(args.keys_filename) - terms_filename = Path(args.terms_filename) - channels_filename = Path(args.channels_filename) - now_path_name = now_br.strftime("%Y-%m-%dT%H") - youtube_keys = read_keys(keys_filename) - channels_groups = read_channels(args.channels_filename) - search_start, search_stop = args.start, args.stop - if isinstance(search_start, str): - search_start = datetime.datetime.fromisoformat(search_start) - if isinstance(search_stop, str): - search_stop = datetime.datetime.fromisoformat(search_stop) - search_start_str = search_start.astimezone(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - search_stop_str = search_stop.astimezone(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - search_limit = args.limit - search_order = args.order - terms_categories = read_csv_dictlist(terms_filename, "categoria", "termo") + Finally, the function executes the appropriate command based on the parsed arguments. If an exception occurs + during the execution of the command, it is caught and raised as an argparse error for proper handling. - print(search_start_str) - print(search_stop_str) + Raises: + argparse.ArgumentError: If the YouTube API key is not provided. + argparse.ArgumentError: If there is an error during the execution of the command. - search_start_br = search_start.astimezone(timezone_br) - result_filename = data_path / f"search_{search_start_br.strftime('%Y-%m-%dT%H')}.csv" - writer = rows.utils.CsvLazyDictWriter(result_filename) - search_results = youtube_search( - terms_categories=terms_categories, - keys=youtube_keys["search"], - start=search_start_str, - stop=search_stop_str, - channels_groups=channels_groups, - order=search_order, - limit=search_limit, - ) - for result in search_results: - writer.writerow(result) - writer.close() + """ + parser = argparse.ArgumentParser(description="CLI Tool for managing YouTube videos add playlists") + parser.add_argument("--api-key", type=str, help="YouTube API Key", dest="api_key") + parser.add_argument("--debug", type=bool, help="Debug mode", dest="debug") + + subparsers = parser.add_subparsers(required=True, dest="command", title="Command", help="Command to be executed") + # cmd_video_search = subparsers.add_parser("video-search", help="Get video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (simplified `video` dict schema or option to get full video info after)") + # cmd_video_comments = subparsers.add_parser("video-comments", help="Get comments from a video ID, generate CSV output (same schema for `comment` dicts)") + # cmd_video_livechat = subparsers.add_parser("video-livechat", help="Get comments from a video ID, generate CSV output (same schema for `chat_message` dicts)") + # cmd_video_transcriptions = subparsers.add_parser("video-transcription", help="Download video transcriptions based on language code, path and list of video IDs or URLs (or CSV filename with URLs/IDs inside), download files to destination and report results") - elif args.command == "video-comments": - print(f"Implement: {args.command}") # TODO: implement + for command in COMMANDS: + command.parse_arguments(subparsers) - elif args.command == "video-livechat": - print(f"Implement: {args.command}") # TODO: implement + args = parser.parse_args() + args.api_key = args.api_key or os.environ.get("YOUTUBE_API_KEY") - elif args.command == "video-transcription": - print(f"Implement: {args.command}") # TODO: implement + if not args.api_key: + parser.error("YouTube API Key is required") + + try: + print(args.func(**args.__dict__)) + except Exception as error: + if args.debug: + raise error + parser.error(error) if __name__ == "__main__": - main() + main() \ No newline at end of file From 4c5d15124a2f54ed56ba13e7d54ee962b7769881 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 25 Jun 2024 11:16:11 -0300 Subject: [PATCH 20/85] - Implemented method to extract URLs from a CSV file; - Implemented method to convert a list of dictionaries into a CSV file or string; --- youtool/commands/base.py | 115 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 youtool/commands/base.py diff --git a/youtool/commands/base.py b/youtool/commands/base.py new file mode 100644 index 0000000..165a48f --- /dev/null +++ b/youtool/commands/base.py @@ -0,0 +1,115 @@ +import csv +import argparse + +from typing import List, Dict, Any, Self +from io import StringIO +from pathlib import Path +from datetime import datetime + + +class Command(): + """ + A base class for commands to inherit from, following a specific structure. + + Attributes: + name (str): The name of the command. + arguments (List[Dict[str, Any]]): A list of dictionaries, each representing an argument for the command. + """ + name: str + arguments: List[Dict[str, Any]] + + @classmethod + def generate_parser(cls: Self, subparsers: argparse._SubParsersAction): + """ + Creates a parser for the command and adds it to the subparsers. + + Args: + subparsers (argparse._SubParsersAction): The subparsers action to add the parser to. + + Returns: + argparse.ArgumentParser: The parser for the command. + """ + return subparsers.add_parser(cls.name, help=cls.__doc__) + + @classmethod + def parse_arguments(cls: Self, subparsers: argparse._SubParsersAction) -> None: + """ + Parses the arguments for the command and sets the command's execute method as the default function to call. + + Args: + subparsers (argparse._SubParsersAction): The subparsers action to add the parser to. + """ + parser = cls.generate_parser(subparsers) + for argument in cls.arguments: + argument_copy = {**argument} + argument_name = argument_copy.pop("name") + parser.add_argument(argument_name, **argument_copy) + parser.set_defaults(func=cls.execute) + + @classmethod + def execute(cls: Self, arguments: argparse.Namespace): + """ + Executes the command. + + This method should be overridden by subclasses to define the command's behavior. + + Args: + arguments (argparse.Namespace): The parsed arguments for the command. + """ + raise NotImplementedError() + + @staticmethod + def data_from_csv(file_path: str, data_column_name: str = None) -> List[str]: + """ + Extracts a list of URLs from a specified CSV file. + + Args: file_path (str): The path to the CSV file containing the URLs. + data_column_name (str, optional): The name of the column in the CSV file that contains the URLs. + If not provided, it defaults to `ChannelId.URL_COLUMN_NAME`. + + Returns: + List[str]: A list of URLs extracted from the specified CSV file. + + Raises: + Exception: If the file path is invalid or the file cannot be found. + """ + data = [] + + file_path = Path(file_path) + if not file_path.is_file(): + raise FileNotFoundError(f"Invalid file path: {file_path}") + + with file_path.open('r', newline='') as csv_file: + reader = csv.DictReader(csv_file) + if data_column_name not in reader.fieldnames: + raise Exception(f"Column {data_column_name} not found on {file_path}") + for row in reader: + data.append(row.get(data_column_name)) + return data + + @classmethod + def data_to_csv(cls: Self, data: List[Dict], output_file_path: str = None) -> str: + """ + Converts a list of channel IDs into a CSV file. + + Parameters: + channels_ids (List[str]): List of channel IDs to be written to the CSV. + output_file_path (str, optional): Path to the file where the CSV will be saved. If not provided, the CSV will be returned as a string. + channel_id_column_name (str, optional): Name of the column in the CSV that will contain the channel IDs. + If not provided, the default value defined in ChannelId.CHANNEL_ID_COLUMN_NAME will be used. + + Returns: + str: The path of the created CSV file or, if no path is provided, the contents of the CSV as a string. + """ + if output_file_path: + output_path = Path(output_file_path) + if output_path.is_dir(): + command_name = cls.name.replace("-", "_") + timestamp = datetime.now().strftime("%M%S%f") + output_file_path = output_path / f"{command_name}_{timestamp}.csv" + + with (Path(output_file_path).open('w', newline='') if output_file_path else StringIO()) as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=list(data[0].keys()) if data else []) + writer.writeheader() + writer.writerows(data) + return str(output_file_path) if output_file_path else csv_file.getvalue() \ No newline at end of file From 943f6b07b3da175e42366e0b05270adb021f0eac Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 25 Jun 2024 11:20:00 -0300 Subject: [PATCH 21/85] - Implemented command to extract YouTube channel IDs from a list of URLs or a CSV file containing URLs; - Added commands directory structure --- youtool/commands/channel_id.py | 85 ++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 youtool/commands/channel_id.py diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py new file mode 100644 index 0000000..2233d33 --- /dev/null +++ b/youtool/commands/channel_id.py @@ -0,0 +1,85 @@ +import csv + +from typing import Self + +from youtool import YouTube + +from .base import Command + + +class ChannelId(Command): + """ + Get channel IDs from a list of URLs (or CSV filename with URLs inside), generate CSV output (just the IDs) + """ + name = "channel-id" + arguments = [ + {"name": "--urls", "type": str, "help": "Channels urls", "nargs": "*"}, + {"name": "--urls-file-path", "type": str, "help": "Channels urls csv file path"}, + {"name": "--output-file-path", "type": str, "help": "Output csv file path"}, + {"name": "--url-column-name", "type": str, "help": "URL column name on csv input files"}, + {"name": "--id-column-name", "type": str, "help": "Channel ID column name on csv output files"} + ] + + URL_COLUMN_NAME: str = "channel_url" + CHANNEL_ID_COLUMN_NAME: str = "channel_id" + + @classmethod + def execute(cls: Self, **kwargs) -> str: + """ + Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. + + This method retrieves YouTube channel IDs from a list of provided URLs or from a file containing URLs. + It then saves these channel IDs to a CSV file if an output file path is specified. + + Args: + urls (list[str], optional): A list of YouTube channel URLs. Either this or urls_file_path must be provided. + urls_file_path (str, optional): Path to a CSV file containing YouTube channel URLs. + Requires url_column_name to specify the column with URLs. + output_file_path (str, optional): Path to the output CSV file where channel IDs will be saved. + If not provided, the result will be returned as a string. + api_key (str): The API key to authenticate with the YouTube Data API. + url_column_name (str, optional): The name of the column in the urls_file_path CSV file that contains the URLs. + Default is "url". + id_column_name (str, optional): The name of the column for channel IDs in the output CSV file. + Default is "channel_id". + + Returns: + str: A message indicating the result of the command. If output_file_path is specified, the message will + include the path to the generated CSV file. Otherwise, it will return the result as a string. + + Raises: + Exception: If neither urls nor urls_file_path is provided. + """ + urls = kwargs.get("urls") + urls_file_path = kwargs.get("urls_file_path") + output_file_path = kwargs.get("output_file_path") + api_key = kwargs.get("api_key") + + url_column_name = kwargs.get("url_column_name") + id_column_name = kwargs.get("id_column_name") + + if urls_file_path and not urls: + urls = cls.data_from_csv( + file_path=urls_file_path, + data_column_name=url_column_name or cls.URL_COLUMN_NAME + ) + + if not urls: + raise Exception("Either 'username' or 'url' must be provided for the channel-id command") + + youtube = YouTube([api_key], disable_ipv6=True) + + channels_ids = [ + youtube.channel_id_from_url(url) for url in urls if url + ] + + result = cls.data_to_csv( + data=[ + { + (id_column_name or cls.CHANNEL_ID_COLUMN_NAME): channel_id for channel_id in channels_ids + } + ], + output_file_path=output_file_path + ) + + return result \ No newline at end of file From b4f82e5bd4e03da73274175c544dcb91a41e6ef4 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 25 Jun 2024 11:22:28 -0300 Subject: [PATCH 22/85] - Added to the list; --- youtool/commands/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 youtool/commands/__init__.py diff --git a/youtool/commands/__init__.py b/youtool/commands/__init__.py new file mode 100644 index 0000000..9d1c702 --- /dev/null +++ b/youtool/commands/__init__.py @@ -0,0 +1,10 @@ +from .channel_id import ChannelId + + +COMMANDS = [ + ChannelId +] + +__all__ = [ + COMMANDS, ChannelId +] \ No newline at end of file From 525015e5d7eb332efdf876f3da4a0b28ba4d9f8f Mon Sep 17 00:00:00 2001 From: Ana Paula Sales Date: Wed, 26 Jun 2024 16:42:07 -0300 Subject: [PATCH 23/85] Update cli.py fix: show error with parser if not in debug mode --- youtool/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/youtool/cli.py b/youtool/cli.py index 6926185..dce4356 100644 --- a/youtool/cli.py +++ b/youtool/cli.py @@ -46,8 +46,8 @@ def main(): except Exception as error: if args.debug: raise error - parser.error(error) + parser.error(error) if __name__ == "__main__": - main() \ No newline at end of file + main() From 4fba6d47b303428b8415c557d3f4c854bfaccdde Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 18:40:48 -0300 Subject: [PATCH 24/85] - Removed the type annotation from the method; - Changed file path passing to use from in the method; --- youtool/commands/base.py | 29 +++++++++++++++++------------ youtool/commands/channel_id.py | 6 +++--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 165a48f..81deb61 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -1,7 +1,7 @@ import csv import argparse -from typing import List, Dict, Any, Self +from typing import List, Dict, Any, Optional from io import StringIO from pathlib import Path from datetime import datetime @@ -19,7 +19,7 @@ class Command(): arguments: List[Dict[str, Any]] @classmethod - def generate_parser(cls: Self, subparsers: argparse._SubParsersAction): + def generate_parser(cls, subparsers: argparse._SubParsersAction): """ Creates a parser for the command and adds it to the subparsers. @@ -32,7 +32,7 @@ def generate_parser(cls: Self, subparsers: argparse._SubParsersAction): return subparsers.add_parser(cls.name, help=cls.__doc__) @classmethod - def parse_arguments(cls: Self, subparsers: argparse._SubParsersAction) -> None: + def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: """ Parses the arguments for the command and sets the command's execute method as the default function to call. @@ -47,7 +47,7 @@ def parse_arguments(cls: Self, subparsers: argparse._SubParsersAction) -> None: parser.set_defaults(func=cls.execute) @classmethod - def execute(cls: Self, arguments: argparse.Namespace): + def execute(cls, arguments: argparse.Namespace): """ Executes the command. @@ -59,36 +59,41 @@ def execute(cls: Self, arguments: argparse.Namespace): raise NotImplementedError() @staticmethod - def data_from_csv(file_path: str, data_column_name: str = None) -> List[str]: + def data_from_csv(file_path: Path, data_column_name: Optional[str] = None) -> List[str]: """ Extracts a list of URLs from a specified CSV file. - Args: file_path (str): The path to the CSV file containing the URLs. - data_column_name (str, optional): The name of the column in the CSV file that contains the URLs. - If not provided, it defaults to `ChannelId.URL_COLUMN_NAME`. + Args: + file_path: The path to the CSV file containing the URLs. + data_column_name: The name of the column in the CSV file that contains the URLs. + If not provided, it defaults to `ChannelId.URL_COLUMN_NAME`. Returns: - List[str]: A list of URLs extracted from the specified CSV file. + A list of URLs extracted from the specified CSV file. Raises: Exception: If the file path is invalid or the file cannot be found. """ data = [] - file_path = Path(file_path) if not file_path.is_file(): raise FileNotFoundError(f"Invalid file path: {file_path}") with file_path.open('r', newline='') as csv_file: reader = csv.DictReader(csv_file) - if data_column_name not in reader.fieldnames: + fieldnames = reader.fieldnames + + if fieldnames is None: + raise ValueError("Fieldnames is None") + + if data_column_name not in fieldnames: raise Exception(f"Column {data_column_name} not found on {file_path}") for row in reader: data.append(row.get(data_column_name)) return data @classmethod - def data_to_csv(cls: Self, data: List[Dict], output_file_path: str = None) -> str: + def data_to_csv(cls, data: List[Dict], output_file_path: Optional[str] = None) -> str: """ Converts a list of channel IDs into a CSV file. diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index 2233d33..c648342 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -1,6 +1,6 @@ import csv -from typing import Self +from pathlib import Path from youtool import YouTube @@ -24,7 +24,7 @@ class ChannelId(Command): CHANNEL_ID_COLUMN_NAME: str = "channel_id" @classmethod - def execute(cls: Self, **kwargs) -> str: + def execute(cls, **kwargs) -> str: """ Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. @@ -60,7 +60,7 @@ def execute(cls: Self, **kwargs) -> str: if urls_file_path and not urls: urls = cls.data_from_csv( - file_path=urls_file_path, + file_path=Path(urls_file_path), data_column_name=url_column_name or cls.URL_COLUMN_NAME ) From 2ba79df4234e90572c289ceb660a85f6bb980138 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 19:22:31 -0300 Subject: [PATCH 25/85] - Add changed the method signature in the class to accept (**kwargs) and return a string; - Added logic to convert values retrieved from the CSV file to strings before appending them to the data list; --- youtool/commands/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 81deb61..6c2ddb0 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -47,7 +47,7 @@ def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: parser.set_defaults(func=cls.execute) @classmethod - def execute(cls, arguments: argparse.Namespace): + def execute(cls, **kwargs) -> str: """ Executes the command. @@ -89,7 +89,9 @@ def data_from_csv(file_path: Path, data_column_name: Optional[str] = None) -> Li if data_column_name not in fieldnames: raise Exception(f"Column {data_column_name} not found on {file_path}") for row in reader: - data.append(row.get(data_column_name)) + value = row.get(data_column_name) + if value is not None: + data.append(str(value)) return data @classmethod From 8ab5185e82d4b460bc70b0807e527e9d78447d30 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 21:16:04 -0300 Subject: [PATCH 26/85] - Fixed typing error in all in the file. --- youtool/commands/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/__init__.py b/youtool/commands/__init__.py index 9d1c702..985024e 100644 --- a/youtool/commands/__init__.py +++ b/youtool/commands/__init__.py @@ -6,5 +6,5 @@ ] __all__ = [ - COMMANDS, ChannelId + "COMMANDS", "ChannelId" ] \ No newline at end of file From 6b283205a6bde8cb04d63725da41dc8dba7f6af1 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:21:05 -0300 Subject: [PATCH 27/85] Add updates docstrings --- youtool/cli.py | 4 +--- youtool/commands/base.py | 24 +++++++++--------------- youtool/commands/channel_id.py | 10 +++------- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/youtool/cli.py b/youtool/cli.py index dce4356..7875342 100644 --- a/youtool/cli.py +++ b/youtool/cli.py @@ -5,8 +5,7 @@ def main(): - """ - Main function for the YouTube CLI Tool. + """Main function for the YouTube CLI Tool. This function sets up the argument parser for the CLI tool, including options for the YouTube API key and command-specific subparsers. It then parses the command-line arguments, retrieving the YouTube API key @@ -19,7 +18,6 @@ def main(): Raises: argparse.ArgumentError: If the YouTube API key is not provided. argparse.ArgumentError: If there is an error during the execution of the command. - """ parser = argparse.ArgumentParser(description="CLI Tool for managing YouTube videos add playlists") parser.add_argument("--api-key", type=str, help="YouTube API Key", dest="api_key") diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 6c2ddb0..5598afd 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -7,9 +7,8 @@ from datetime import datetime -class Command(): - """ - A base class for commands to inherit from, following a specific structure. +class Command: + """A base class for commands to inherit from, following a specific structure. Attributes: name (str): The name of the command. @@ -20,8 +19,7 @@ class Command(): @classmethod def generate_parser(cls, subparsers: argparse._SubParsersAction): - """ - Creates a parser for the command and adds it to the subparsers. + """Creates a parser for the command and adds it to the subparsers. Args: subparsers (argparse._SubParsersAction): The subparsers action to add the parser to. @@ -33,8 +31,7 @@ def generate_parser(cls, subparsers: argparse._SubParsersAction): @classmethod def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: - """ - Parses the arguments for the command and sets the command's execute method as the default function to call. + """Parses the arguments for the command and sets the command's execute method as the default function to call. Args: subparsers (argparse._SubParsersAction): The subparsers action to add the parser to. @@ -47,9 +44,8 @@ def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: parser.set_defaults(func=cls.execute) @classmethod - def execute(cls, **kwargs) -> str: - """ - Executes the command. + def execute(cls, **kwargs) -> str: # noqa: D417 + """Executes the command. This method should be overridden by subclasses to define the command's behavior. @@ -60,8 +56,7 @@ def execute(cls, **kwargs) -> str: @staticmethod def data_from_csv(file_path: Path, data_column_name: Optional[str] = None) -> List[str]: - """ - Extracts a list of URLs from a specified CSV file. + """Extracts a list of URLs from a specified CSV file. Args: file_path: The path to the CSV file containing the URLs. @@ -96,13 +91,12 @@ def data_from_csv(file_path: Path, data_column_name: Optional[str] = None) -> Li @classmethod def data_to_csv(cls, data: List[Dict], output_file_path: Optional[str] = None) -> str: - """ - Converts a list of channel IDs into a CSV file. + """Converts a list of channel IDs into a CSV file. Parameters: channels_ids (List[str]): List of channel IDs to be written to the CSV. output_file_path (str, optional): Path to the file where the CSV will be saved. If not provided, the CSV will be returned as a string. - channel_id_column_name (str, optional): Name of the column in the CSV that will contain the channel IDs. + channel_id_column_name (str, optional): Name of the column in the CSV that will contain the channel IDs. If not provided, the default value defined in ChannelId.CHANNEL_ID_COLUMN_NAME will be used. Returns: diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index c648342..8e1d004 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -1,4 +1,3 @@ -import csv from pathlib import Path @@ -8,9 +7,7 @@ class ChannelId(Command): - """ - Get channel IDs from a list of URLs (or CSV filename with URLs inside), generate CSV output (just the IDs) - """ + """Get channel IDs from a list of URLs (or CSV filename with URLs inside), generate CSV output (just the IDs).""" name = "channel-id" arguments = [ {"name": "--urls", "type": str, "help": "Channels urls", "nargs": "*"}, @@ -24,9 +21,8 @@ class ChannelId(Command): CHANNEL_ID_COLUMN_NAME: str = "channel_id" @classmethod - def execute(cls, **kwargs) -> str: - """ - Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. + def execute(cls, **kwargs) -> str: # noqa: D417 + """Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. This method retrieves YouTube channel IDs from a list of provided URLs or from a file containing URLs. It then saves these channel IDs to a CSV file if an output file path is specified. From dfc2011450d48e18effe62f2338947ad72944e8c Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 00:52:05 -0300 Subject: [PATCH 28/85] Update import --- youtool/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/cli.py b/youtool/cli.py index 7875342..961d2e6 100644 --- a/youtool/cli.py +++ b/youtool/cli.py @@ -1,7 +1,7 @@ import argparse import os -from commands import COMMANDS +from youtool.commands import COMMANDS def main(): From b1b33670fdebed9e5418ea9ea1824547f25b302a Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 00:53:41 -0300 Subject: [PATCH 29/85] Add update command into the file --- youtool/commands/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/youtool/commands/__init__.py b/youtool/commands/__init__.py index 985024e..89bbc09 100644 --- a/youtool/commands/__init__.py +++ b/youtool/commands/__init__.py @@ -1,10 +1,10 @@ +from .base import Command from .channel_id import ChannelId - COMMANDS = [ ChannelId ] __all__ = [ - "COMMANDS", "ChannelId" -] \ No newline at end of file + "Command", "COMMANDS", "ChannelId", +] From 28b2574278a16e4b28cf0aeaa88347881f09f2fd Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 00:55:04 -0300 Subject: [PATCH 30/85] Add update --- youtool/commands/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 5598afd..077c826 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -113,4 +113,4 @@ def data_to_csv(cls, data: List[Dict], output_file_path: Optional[str] = None) - writer = csv.DictWriter(csv_file, fieldnames=list(data[0].keys()) if data else []) writer.writeheader() writer.writerows(data) - return str(output_file_path) if output_file_path else csv_file.getvalue() \ No newline at end of file + return str(output_file_path) if output_file_path else csv_file.getvalue() From fe180fb7efa5b1663ce413a816332ec7231a58a0 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 00:56:19 -0300 Subject: [PATCH 31/85] Add improvements to the file --- youtool/commands/channel_id.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index 8e1d004..d42f311 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -54,14 +54,7 @@ def execute(cls, **kwargs) -> str: # noqa: D417 url_column_name = kwargs.get("url_column_name") id_column_name = kwargs.get("id_column_name") - if urls_file_path and not urls: - urls = cls.data_from_csv( - file_path=Path(urls_file_path), - data_column_name=url_column_name or cls.URL_COLUMN_NAME - ) - - if not urls: - raise Exception("Either 'username' or 'url' must be provided for the channel-id command") + urls = cls.resolve_urls(urls, urls_file_path, url_column_name) youtube = YouTube([api_key], disable_ipv6=True) @@ -72,10 +65,22 @@ def execute(cls, **kwargs) -> str: # noqa: D417 result = cls.data_to_csv( data=[ { - (id_column_name or cls.CHANNEL_ID_COLUMN_NAME): channel_id for channel_id in channels_ids - } + (id_column_name or cls.CHANNEL_ID_COLUMN_NAME): channel_id + } for channel_id in channels_ids ], output_file_path=output_file_path ) - return result \ No newline at end of file + return result + + @classmethod + def resolve_urls(cls, urls, urls_file_path, url_column_name): + if urls_file_path and not urls: + urls = cls.data_from_csv( + file_path=Path(urls_file_path), + data_column_name=url_column_name or cls.URL_COLUMN_NAME + ) + + if not urls: + raise Exception("Either 'username' or 'url' must be provided for the channel-id command") + return urls From d4e66b4209628a8e28c6c8ec43c3f93f3de93a64 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 00:58:13 -0300 Subject: [PATCH 32/85] Add test for cli file --- tests/test_cli.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/test_cli.py diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..3a489ee --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,20 @@ +import pytest + +from subprocess import run + +from youtool.commands import COMMANDS + +from youtool.commands.base import Command + + +@pytest.mark.parametrize( + "command", COMMANDS +) +def test_missing_api_key(monkeypatch: pytest.MonkeyPatch, command: Command): + monkeypatch.delenv('YOUTUBE_API_KEY', raising=False) + cli_path = "youtool/cli.py" + command = ["python", cli_path, command.name] + result = run(command, capture_output=True, text=True, check=False) + + assert result.returncode == 2 + assert "YouTube API Key is required" in result.stderr \ No newline at end of file From 4bf29ff4e4c6f7ab8143d2a424cac2b972b669b9 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 00:59:00 -0300 Subject: [PATCH 33/85] Add test for base file --- tests/commands/__init__.py | 0 tests/commands/test_base.py | 127 ++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tests/commands/__init__.py create mode 100644 tests/commands/test_base.py diff --git a/tests/commands/__init__.py b/tests/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py new file mode 100644 index 0000000..9d3ad90 --- /dev/null +++ b/tests/commands/test_base.py @@ -0,0 +1,127 @@ +import csv +import argparse +import pytest + +from io import StringIO +from datetime import datetime +from pathlib import Path +from unittest.mock import MagicMock, patch, mock_open +from youtool.commands import Command + + +class TestCommand(Command): + name = "command_name" + arguments = [ + {"name": "--test-arg", "help": "Test argument", "default": "default_value", "type": str} + ] + + @classmethod + def execute(cls, **kwargs): + return "executed" + +@pytest.fixture +def subparsers(): + parser = argparse.ArgumentParser() + return parser.add_subparsers() + + +def test_generate_parser(subparsers): + parser = TestCommand.generate_parser(subparsers) + + assert parser is not None, "Parser should not be None" + assert isinstance(parser, argparse.ArgumentParser), "Parser should be an instance of argparse.ArgumentParser" + assert parser.prog.endswith(TestCommand.name), f"Parser prog should end with '{TestCommand.name}'" + + +def test_parse_arguments(subparsers): + subparsers_mock = MagicMock(spec=subparsers) + + TestCommand.parse_arguments(subparsers_mock) + + subparsers_mock.add_parser.assert_called_once_with(TestCommand.name, help=TestCommand.__doc__) + parser_mock = subparsers_mock.add_parser.return_value + parser_mock.add_argument.assert_called_once_with("--test-arg", help="Test argument", default="default_value", type=str) + parser_mock.set_defaults.assert_called_once_with(func=TestCommand.execute) + + +def test_command(): + class MyCommand(Command): + pass + + with pytest.raises(NotImplementedError): + MyCommand.execute() + + +@pytest.fixture +def mock_csv_file(): + + csv_content = """URL + http://example.com + http://example2.com + """ + return csv_content + +def test_data_from_csv_valid(mock_csv_file): + with patch('pathlib.Path.is_file', return_value=True): + with patch('builtins.open', mock_open(read_data=mock_csv_file)): + data_column_name = "URL" + file_path = Path("tests/commands/csv_valid.csv") + result = Command.data_from_csv(file_path, data_column_name) + assert len(result) == 2 + assert result[0] == "http://example.com" + assert result[1] == "http://example2.com" + +def test_data_from_csv_file_not_found(): + with patch('pathlib.Path.is_file', return_value=False): + file_path = Path("/fake/path/not_found.csv") + with pytest.raises(FileNotFoundError): + Command.data_from_csv(file_path, "URL") + +def test_data_from_csv_column_not_found(mock_csv_file): + with patch('pathlib.Path.is_file', return_value=True): + with patch('builtins.open', mock_open(read_data=mock_csv_file)): + file_path = Path("tests/commands/csv_column_not_found.csv") + with pytest.raises(Exception) as exc_info: + Command.data_from_csv(file_path, "NonExistentColumn") + assert "Column NonExistentColumn not found on tests/commands/csv_column_not_found.csv" in str(exc_info.value), "Exception message should contain column not found error" + + +@pytest.fixture +def sample_data(): + return [ + {"id": "123", "name": "Channel One"}, + {"id": "456", "name": "Channel Two"} + ] + +def test_data_to_csv_with_output_file_path(tmp_path, sample_data): + output_file_path = tmp_path / "output.csv" + + result_path = Command.data_to_csv(sample_data, str(output_file_path)) + + assert result_path == str(output_file_path), "The returned path should match the provided output file path" + assert output_file_path.exists(), "The output file should exist" + with output_file_path.open('r') as f: + reader = csv.DictReader(f) + rows = list(reader) + assert len(rows) == 2, "There should be two rows in the output CSV" + assert rows[0]["id"] == "123" and rows[1]["id"] == "456", "The IDs should match the sample data" + +def test_data_to_csv_without_output_file_path(sample_data): + csv_content = Command.data_to_csv(sample_data) + + assert "id,name" in csv_content + assert "123,Channel One" in csv_content + assert "456,Channel Two" in csv_content + +def test_data_to_csv_output(tmp_path): + output_file_path = tmp_path / "output.csv" + + data = [ + {"id": 1, "name": "Test1"}, + {"id": 2, "name": "Test2"} + ] + + expected_output = "id,name\n1,Test1\n2,Test2\n" + result = Command.data_to_csv(data, str(output_file_path)) + assert Path(output_file_path).is_file() + assert expected_output == Path(output_file_path).read_text() From 216e5f2da8753ca2c00e61d00092e4baeb0e060e Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 00:59:45 -0300 Subject: [PATCH 34/85] Add test for channel_id command --- tests/commands/test_channel_id.py | 55 +++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/commands/test_channel_id.py diff --git a/tests/commands/test_channel_id.py b/tests/commands/test_channel_id.py new file mode 100644 index 0000000..56035ee --- /dev/null +++ b/tests/commands/test_channel_id.py @@ -0,0 +1,55 @@ +import csv +import pytest + +from io import StringIO + +from unittest.mock import patch, call +from youtool.commands.channel_id import ChannelId + +@pytest.fixture +def csv_file(tmp_path): + csv_content = "channel_url\nhttps://www.youtube.com/@Turicas/featured\n" + csv_file = tmp_path / "urls.csv" + csv_file.write_text(csv_content) + return csv_file + +@pytest.fixture +def youtube_api_mock(): + with patch("youtool.commands.channel_id.YouTube") as mock: + mock.return_value.channel_id_from_url.side_effect = lambda url: f"channel-{url}" + yield mock + +def test_channels_ids_csv_preparation(youtube_api_mock): + urls = ["https://www.youtube.com/@Turicas/featured", "https://www.youtube.com/c/PythonicCaf%C3%A9"] + api_key = "test_api_key" + id_column_name = "custom_id_column" + expected_result_data = [ + {id_column_name: "channel-https://www.youtube.com/@Turicas/featured"}, + {id_column_name: "channel-https://www.youtube.com/c/PythonicCaf%C3%A9"} + ] + with StringIO() as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=[id_column_name]) + writer.writeheader() + writer.writerows(expected_result_data) + expected_result_csv = csv_file.getvalue() + + result = ChannelId.execute(urls=urls, api_key=api_key, id_column_name=id_column_name) + + youtube_api_mock.return_value.channel_id_from_url.assert_has_calls([call(url) for url in urls], any_order=True) + assert result == expected_result_csv + + +def test_resolve_urls_with_direct_urls(): + # Tests whether the function returns the directly given list of URLs. + urls = ["https://www.youtube.com/@Turicas/featured"] + result = ChannelId.resolve_urls(urls, None, None) + assert result == urls + +def test_resolve_urls_with_file_path(csv_file): + result = ChannelId.resolve_urls(None, csv_file, "channel_url") + assert result == ["https://www.youtube.com/@Turicas/featured"] + +def test_resolve_urls_raises_exception(): + # Tests whether the function throws an exception when neither urls nor urls_file_path are provided. + with pytest.raises(Exception, match="Either 'username' or 'url' must be provided for the channel-id command"): + ChannelId.resolve_urls(None, None, None) From 1b335b7f184de9c0c6b2678d050072de4c6d5d95 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 15:26:20 -0300 Subject: [PATCH 35/85] add docstrings --- tests/commands/test_base.py | 47 +++++++++++++++++++++++++++++++ tests/commands/test_channel_id.py | 29 +++++++++++++++++-- tests/test_cli.py | 5 ++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py index 9d3ad90..e9265e8 100644 --- a/tests/commands/test_base.py +++ b/tests/commands/test_base.py @@ -21,11 +21,17 @@ def execute(cls, **kwargs): @pytest.fixture def subparsers(): + """Fixture to create subparsers for argument parsing.""" parser = argparse.ArgumentParser() return parser.add_subparsers() def test_generate_parser(subparsers): + """Test to verify the parser generation. + + This test checks if the `generate_parser` method correctly generates a parser + for the command and sets the appropriate properties + """ parser = TestCommand.generate_parser(subparsers) assert parser is not None, "Parser should not be None" @@ -34,6 +40,11 @@ def test_generate_parser(subparsers): def test_parse_arguments(subparsers): + """Test to verify argument parsing. + + This test checks if the `parse_arguments` method correctly adds the command's + arguments to the parser and sets the default function to the command's execute method. + """ subparsers_mock = MagicMock(spec=subparsers) TestCommand.parse_arguments(subparsers_mock) @@ -45,6 +56,11 @@ def test_parse_arguments(subparsers): def test_command(): + """Test to verify that the `execute` method is implemented. + + This test ensures that if a command does not implement the `execute` method, + a `NotImplementedError` is raised. + """ class MyCommand(Command): pass @@ -54,6 +70,7 @@ class MyCommand(Command): @pytest.fixture def mock_csv_file(): + """Fixture to provide mock CSV content for tests.""" csv_content = """URL http://example.com @@ -62,6 +79,14 @@ def mock_csv_file(): return csv_content def test_data_from_csv_valid(mock_csv_file): + """Test to verify reading data from a valid CSV file. + + This test checks if the `data_from_csv` method correctly reads data from a valid CSV file + and returns the expected list of URLs. + + Args: + mock_csv_file (str): The mock CSV file content. + """ with patch('pathlib.Path.is_file', return_value=True): with patch('builtins.open', mock_open(read_data=mock_csv_file)): data_column_name = "URL" @@ -72,6 +97,11 @@ def test_data_from_csv_valid(mock_csv_file): assert result[1] == "http://example2.com" def test_data_from_csv_file_not_found(): + """Test to verify behavior when the specified column is not found in the CSV file. + + This test checks if the `data_from_csv` method raises an exception when the specified + column does not exist in the CSV file. + """ with patch('pathlib.Path.is_file', return_value=False): file_path = Path("/fake/path/not_found.csv") with pytest.raises(FileNotFoundError): @@ -88,12 +118,18 @@ def test_data_from_csv_column_not_found(mock_csv_file): @pytest.fixture def sample_data(): + """Fixture to provide sample data for tests.""" return [ {"id": "123", "name": "Channel One"}, {"id": "456", "name": "Channel Two"} ] def test_data_to_csv_with_output_file_path(tmp_path, sample_data): + """Test to verify writing data to a CSV file with an output file path specified. + + This test checks if the `data_to_csv` method correctly writes the sample data to + a CSV file when an output file path is provided. + """ output_file_path = tmp_path / "output.csv" result_path = Command.data_to_csv(sample_data, str(output_file_path)) @@ -107,6 +143,11 @@ def test_data_to_csv_with_output_file_path(tmp_path, sample_data): assert rows[0]["id"] == "123" and rows[1]["id"] == "456", "The IDs should match the sample data" def test_data_to_csv_without_output_file_path(sample_data): + """Test to verify writing data to a CSV format without an output file path specified. + + This test checks if the `data_to_csv` method correctly returns the CSV content + as a string when no output file path is provided. + """ csv_content = Command.data_to_csv(sample_data) assert "id,name" in csv_content @@ -114,6 +155,12 @@ def test_data_to_csv_without_output_file_path(sample_data): assert "456,Channel Two" in csv_content def test_data_to_csv_output(tmp_path): + """ + Test to verify the content of the output CSV file. + + This test checks if the `data_to_csv` method writes the expected content + to the output CSV file. + """ output_file_path = tmp_path / "output.csv" data = [ diff --git a/tests/commands/test_channel_id.py b/tests/commands/test_channel_id.py index 56035ee..04400ef 100644 --- a/tests/commands/test_channel_id.py +++ b/tests/commands/test_channel_id.py @@ -8,6 +8,8 @@ @pytest.fixture def csv_file(tmp_path): + """Fixture to create a temporary CSV file with a single YouTube channel URL.""" + csv_content = "channel_url\nhttps://www.youtube.com/@Turicas/featured\n" csv_file = tmp_path / "urls.csv" csv_file.write_text(csv_content) @@ -15,11 +17,21 @@ def csv_file(tmp_path): @pytest.fixture def youtube_api_mock(): + """Fixture to mock the YouTube API. + + This fixture mocks the `YouTube` class and its `channel_id_from_url` method + to return a channel ID based on the URL. + """ with patch("youtool.commands.channel_id.YouTube") as mock: mock.return_value.channel_id_from_url.side_effect = lambda url: f"channel-{url}" yield mock def test_channels_ids_csv_preparation(youtube_api_mock): + """Fixture to mock the YouTube API. + + This fixture mocks the `YouTube` class and its `channel_id_from_url` method + to return a channel ID based on the URL. + """ urls = ["https://www.youtube.com/@Turicas/featured", "https://www.youtube.com/c/PythonicCaf%C3%A9"] api_key = "test_api_key" id_column_name = "custom_id_column" @@ -40,16 +52,29 @@ def test_channels_ids_csv_preparation(youtube_api_mock): def test_resolve_urls_with_direct_urls(): - # Tests whether the function returns the directly given list of URLs. + """Test to verify resolving URLs when provided directly. + + This test checks if the `resolve_urls` method of the `ChannelId` class correctly + returns the given list of URLs when provided directly. + """ urls = ["https://www.youtube.com/@Turicas/featured"] result = ChannelId.resolve_urls(urls, None, None) assert result == urls def test_resolve_urls_with_file_path(csv_file): + """Test to verify resolving URLs from a CSV file. + + This test checks if the `resolve_urls` method of the `ChannelId` class correctly + reads URLs from a given CSV file. + """ result = ChannelId.resolve_urls(None, csv_file, "channel_url") assert result == ["https://www.youtube.com/@Turicas/featured"] def test_resolve_urls_raises_exception(): - # Tests whether the function throws an exception when neither urls nor urls_file_path are provided. + """Test to verify exception raising when no URLs are provided. + + This test checks if the `resolve_urls` method of the `ChannelId` class raises an exception + when neither direct URLs nor a file path are provided. + """ with pytest.raises(Exception, match="Either 'username' or 'url' must be provided for the channel-id command"): ChannelId.resolve_urls(None, None, None) diff --git a/tests/test_cli.py b/tests/test_cli.py index 3a489ee..9165041 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -11,6 +11,11 @@ "command", COMMANDS ) def test_missing_api_key(monkeypatch: pytest.MonkeyPatch, command: Command): + """Test to verify behavior when the YouTube API key is missing. + + This test ensures that when the YouTube API key is not set, running any command + from the youtool CLI results in an appropriate error message and exit code. + """ monkeypatch.delenv('YOUTUBE_API_KEY', raising=False) cli_path = "youtool/cli.py" command = ["python", cli_path, command.name] From c5ad8fd7302c85af58972e850f297de202f2ff6a Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 25 Jun 2024 14:45:38 -0300 Subject: [PATCH 36/85] - Implement ChannelInfo class to fetch YouTube channel information from URLs, usernames, or CSV files containing them; - Add method to filter channel information based on specified columns; - Define method to handle the command logic, including reading input, fetching channel data, and saving to CSV; - Support for various input methods including direct URLs/usernames and file paths for CSV input; - Support for specifying output CSV file path and columns to include in the output. --- youtool/commands/channel_info.py | 120 +++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 youtool/commands/channel_info.py diff --git a/youtool/commands/channel_info.py b/youtool/commands/channel_info.py new file mode 100644 index 0000000..493ef82 --- /dev/null +++ b/youtool/commands/channel_info.py @@ -0,0 +1,120 @@ +import csv + +from typing import List, Dict, Optional, Self + +from youtool import YouTube + +from .base import Command + + +class ChannelInfo(Command): + """ + Get channel info from a list of IDs (or CSV filename with IDs inside), generate CSV output + (same schema for `channel` dicts) + """ + name = "channel-info" + arguments = [ + {"name": "--urls", "type": str, "help": "Channel URLs", "nargs": "*"}, + {"name": "--usernames", "type": str, "help": "Channel usernames", "nargs": "*"}, + {"name": "--ids", "type": str, "help": "Channel IDs", "nargs": "*"}, + {"name": "--urls-file-path", "type": str, "help": "Channel URLs CSV file path"}, + {"name": "--usernames-file-path", "type": str, "help": "Channel usernames CSV file path"}, + {"name": "--ids-file-path", "type": str, "help": "Channel IDs CSV file path"}, + {"name": "--output-file-path", "type": str, "help": "Output CSV file path"}, + {"name": "--url-column-name", "type": str, "help": "URL column name on CSV input files"}, + {"name": "--username-column-name", "type": str, "help": "Username column name on CSV input files"}, + {"name": "--id-column-name", "type": str, "help": "ID column name on CSV input files"}, + ] + + URL_COLUMN_NAME: str = "channel_url" + USERNAME_COLUMN_NAME: str = "channel_username" + ID_COLUMN_NAME: str = "channel_id" + INFO_COLUMNS: List[str] = [ + "id", "title", "description", "published_at", "view_count", "subscriber_count", "video_count" + ] + + @staticmethod + def filter_fields(channel_info: Dict, info_columns: Optional[List] = None): + """ + Filters the fields of a dictionary containing channel information based on + specified columns. + + Args: + channel_info (Dict): A dictionary containing channel information. + info_columns (Optional[List], optional): A list specifying which fields + to include in the filtered output. If None, returns the entire + channel_info dictionary. Defaults to None. + + Returns: + Dict: A dictionary containing only the fields specified in info_columns + (if provided) or the entire channel_info dictionary if info_columns is None. + """ + return { + field: value for field, value in channel_info.items() if field in info_columns + } if info_columns else channel_info + + @classmethod + def execute(cls: Self, **kwargs) -> str: + """ + Execute the channel-info command to fetch YouTube channel information from URLs or usernames and save them to a CSV file. + + Args: + urls (list[str], optional): A list of YouTube channel URLs. If not provided, `urls_file_path` must be specified. + usernames (list[str], optional): A list of YouTube channel usernames. If not provided, `usernames_file_path` must be specified. + urls_file_path (str, optional): Path to a CSV file containing YouTube channel URLs. + usernames_file_path (str, optional): Path to a CSV file containing YouTube channel usernames. + output_file_path (str, optional): Path to the output CSV file where channel information will be saved. + api_key (str): The API key to authenticate with the YouTube Data API. + url_column_name (str, optional): The name of the column in the `urls_file_path` CSV file that contains the URLs. + Default is "channel_url". + username_column_name (str, optional): The name of the column in the `usernames_file_path` CSV file that contains the usernames. + Default is "channel_username". + info_columns (str, optional): Comma-separated list of columns to include in the output CSV. Default is the class attribute `INFO_COLUMNS`. + + Returns: + str: A message indicating the result of the command. If `output_file_path` is specified, the message will + include the path to the generated CSV file. Otherwise, it will return the result as a string. + + Raises: + Exception: If neither `urls`, `usernames`, `urls_file_path` nor `usernames_file_path` is provided. + """ + urls = kwargs.get("urls") + usernames = kwargs.get("usernames") + urls_file_path = kwargs.get("urls_file_path") + usernames_file_path = kwargs.get("usernames_file_path") + output_file_path = kwargs.get("output_file_path") + api_key = kwargs.get("api_key") + + url_column_name = kwargs.get("url_column_name") + username_column_name = kwargs.get("username_column_name") + info_columns = kwargs.get("info_columns") + + info_columns = [ + column.strip() for column in info_columns.split(",") + ] if info_columns else ChannelInfo.INFO_COLUMNS + + if urls_file_path and not urls: + urls = ChannelInfo.data_from_file(urls_file_path, url_column_name) + if usernames_file_path and not usernames: + usernames = ChannelInfo.data_from_file(usernames_file_path, username_column_name) + + if not urls and not usernames: + raise Exception("Either 'urls' or 'usernames' must be provided for the channel-info command") + + youtube = YouTube([api_key], disable_ipv6=True) + + channels_ids = [ + youtube.channel_id_from_url(url) for url in (urls or []) if url + ] + [ + youtube.channel_id_from_username(username) for username in (usernames or []) if username + ] + channel_ids = [channel_id for channel_id in channels_ids if channel_id] + + return cls.data_to_csv( + data=[ + ChannelInfo.filter_fields( + channel_info, info_columns + ) for channel_info in (youtube.channels_infos(channel_ids) or []) + ], + output_file_path=output_file_path + ) \ No newline at end of file From e718d4a1acc2482395ede78a16353a5a32138def Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 25 Jun 2024 14:47:42 -0300 Subject: [PATCH 37/85] - Included ChannelInfo in the list of commands in COMMANDS. --- youtool/commands/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/youtool/commands/__init__.py b/youtool/commands/__init__.py index 89bbc09..1939a22 100644 --- a/youtool/commands/__init__.py +++ b/youtool/commands/__init__.py @@ -1,10 +1,12 @@ from .base import Command from .channel_id import ChannelId +from .channel_info import ChannelInfo COMMANDS = [ - ChannelId + ChannelId, + ChannelInfo ] __all__ = [ - "Command", "COMMANDS", "ChannelId", + COMMANDS, ChannelId, ChannelInfo ] From 7dc7b8d297122045191ccc7d94d90170f15518bf Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:21:05 -0300 Subject: [PATCH 38/85] Add updates docstrings --- youtool/commands/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 077c826..275c282 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -44,7 +44,7 @@ def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: parser.set_defaults(func=cls.execute) @classmethod - def execute(cls, **kwargs) -> str: # noqa: D417 + def execute(cls, **kwargs) -> str: """Executes the command. This method should be overridden by subclasses to define the command's behavior. From ed012e55368eed19b93f534bd85b72726b44248b Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:22:26 -0300 Subject: [PATCH 39/85] Add updates docstrings --- youtool/commands/channel_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index d42f311..c599982 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -21,7 +21,7 @@ class ChannelId(Command): CHANNEL_ID_COLUMN_NAME: str = "channel_id" @classmethod - def execute(cls, **kwargs) -> str: # noqa: D417 + def execute(cls, **kwargs) -> str: """Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. This method retrieves YouTube channel IDs from a list of provided URLs or from a file containing URLs. From 9a5fe66e52486d3fa7840cfd1b7f98d4a79cf5ee Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:51:39 -0300 Subject: [PATCH 40/85] - Add updates --- youtool/commands/channel_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index c599982..d42f311 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -21,7 +21,7 @@ class ChannelId(Command): CHANNEL_ID_COLUMN_NAME: str = "channel_id" @classmethod - def execute(cls, **kwargs) -> str: + def execute(cls, **kwargs) -> str: # noqa: D417 """Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. This method retrieves YouTube channel IDs from a list of provided URLs or from a file containing URLs. From 8ba47cf3a9a20f0544964d29db64607287e260fe Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:53:51 -0300 Subject: [PATCH 41/85] - Add updates --- youtool/commands/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 275c282..077c826 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -44,7 +44,7 @@ def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: parser.set_defaults(func=cls.execute) @classmethod - def execute(cls, **kwargs) -> str: + def execute(cls, **kwargs) -> str: # noqa: D417 """Executes the command. This method should be overridden by subclasses to define the command's behavior. From c08e4ecf7090c1e458ff2238578a8195e450d725 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 13:57:55 -0300 Subject: [PATCH 42/85] - Add test for channel_info command; - Add update channel_info file; - fix test_base --- tests/commands/test_base.py | 16 ++++----- tests/commands/test_channel_info.py | 53 +++++++++++++++++++++++++++++ youtool/commands/channel_info.py | 6 ++-- 3 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 tests/commands/test_channel_info.py diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py index e9265e8..e15c787 100644 --- a/tests/commands/test_base.py +++ b/tests/commands/test_base.py @@ -2,8 +2,6 @@ import argparse import pytest -from io import StringIO -from datetime import datetime from pathlib import Path from unittest.mock import MagicMock, patch, mock_open from youtool.commands import Command @@ -90,7 +88,7 @@ def test_data_from_csv_valid(mock_csv_file): with patch('pathlib.Path.is_file', return_value=True): with patch('builtins.open', mock_open(read_data=mock_csv_file)): data_column_name = "URL" - file_path = Path("tests/commands/csv_valid.csv") + file_path = Path("tests/resources/csv_valid.csv") result = Command.data_from_csv(file_path, data_column_name) assert len(result) == 2 assert result[0] == "http://example.com" @@ -110,10 +108,10 @@ def test_data_from_csv_file_not_found(): def test_data_from_csv_column_not_found(mock_csv_file): with patch('pathlib.Path.is_file', return_value=True): with patch('builtins.open', mock_open(read_data=mock_csv_file)): - file_path = Path("tests/commands/csv_column_not_found.csv") + file_path = Path("tests/resources/csv_column_not_found.csv") with pytest.raises(Exception) as exc_info: Command.data_from_csv(file_path, "NonExistentColumn") - assert "Column NonExistentColumn not found on tests/commands/csv_column_not_found.csv" in str(exc_info.value), "Exception message should contain column not found error" + assert "Column NonExistentColumn not found on tests/resources/csv_column_not_found.csv" in str(exc_info.value) @pytest.fixture @@ -134,13 +132,13 @@ def test_data_to_csv_with_output_file_path(tmp_path, sample_data): result_path = Command.data_to_csv(sample_data, str(output_file_path)) - assert result_path == str(output_file_path), "The returned path should match the provided output file path" - assert output_file_path.exists(), "The output file should exist" + assert result_path == str(output_file_path) + assert output_file_path.exists() with output_file_path.open('r') as f: reader = csv.DictReader(f) rows = list(reader) - assert len(rows) == 2, "There should be two rows in the output CSV" - assert rows[0]["id"] == "123" and rows[1]["id"] == "456", "The IDs should match the sample data" + assert len(rows) == 2 + assert rows[0]["id"] == "123" and rows[1]["id"] == "456" def test_data_to_csv_without_output_file_path(sample_data): """Test to verify writing data to a CSV format without an output file path specified. diff --git a/tests/commands/test_channel_info.py b/tests/commands/test_channel_info.py new file mode 100644 index 0000000..06b3a66 --- /dev/null +++ b/tests/commands/test_channel_info.py @@ -0,0 +1,53 @@ +import pytest + +from unittest.mock import patch, Mock, call + +from youtool.commands.channel_info import ChannelInfo, YouTube + + +def test_filter_fields(): + channel_info = { + 'channel_id': '123456', + 'channel_name': 'Test Channel', + 'subscribers': 1000, + 'videos': 50, + 'category': 'Tech' + } + + info_columns = ['channel_id', 'channel_name', 'subscribers'] + filtered_info = ChannelInfo.filter_fields(channel_info, info_columns) + + expected_result = { + 'channel_id': '123456', + 'channel_name': 'Test Channel', + 'subscribers': 1000 + } + + assert filtered_info == expected_result, f"Expected {expected_result}, but got {filtered_info}" + + +def test_channel_ids_from_urls_and_usernames(mocker): + urls = ["https://www.youtube.com/@Turicas/featured", "https://www.youtube.com/c/PythonicCaf%C3%A9"] + usernames = ["Turicas", "PythonicCafe"] + + ids_from_urls_mock = "id_from_url" + ids_from_usernames_mock = "id_from_username" + youtube_mock = mocker.patch("youtool.commands.channel_info.YouTube") + + channel_id_from_url_mock = Mock(return_value=ids_from_urls_mock) + channel_id_from_username_mock = Mock(return_value=ids_from_usernames_mock) + channels_infos_mock = Mock(return_value=[]) + + youtube_mock.return_value.channel_id_from_url = channel_id_from_url_mock + youtube_mock.return_value.channel_id_from_username = channel_id_from_username_mock + youtube_mock.return_value.channels_infos = channels_infos_mock + + ChannelInfo.execute(urls=urls, usernames=usernames) + + channel_id_from_url_mock.assert_has_calls( + [call(url) for url in urls] + ) + channel_id_from_username_mock.assert_has_calls( + [call(username) for username in usernames] + ) + channels_infos_mock.assert_called_once_with([ids_from_urls_mock, ids_from_usernames_mock]) diff --git a/youtool/commands/channel_info.py b/youtool/commands/channel_info.py index 493ef82..fb0944e 100644 --- a/youtool/commands/channel_info.py +++ b/youtool/commands/channel_info.py @@ -108,7 +108,9 @@ def execute(cls: Self, **kwargs) -> str: ] + [ youtube.channel_id_from_username(username) for username in (usernames or []) if username ] - channel_ids = [channel_id for channel_id in channels_ids if channel_id] + channel_ids = list( + set([channel_id for channel_id in channels_ids if channel_id]) + ) return cls.data_to_csv( data=[ @@ -117,4 +119,4 @@ def execute(cls: Self, **kwargs) -> str: ) for channel_info in (youtube.channels_infos(channel_ids) or []) ], output_file_path=output_file_path - ) \ No newline at end of file + ) From a5bb13d54c0aa8474126fe923c026bb6ab268974 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 15:34:28 -0300 Subject: [PATCH 43/85] add docstrings --- tests/commands/test_channel_info.py | 16 ++++++++++++---- youtool/commands/channel_info.py | 14 +++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/commands/test_channel_info.py b/tests/commands/test_channel_info.py index 06b3a66..5e6ef33 100644 --- a/tests/commands/test_channel_info.py +++ b/tests/commands/test_channel_info.py @@ -1,11 +1,14 @@ -import pytest +from unittest.mock import Mock, call -from unittest.mock import patch, Mock, call - -from youtool.commands.channel_info import ChannelInfo, YouTube +from youtool.commands.channel_info import ChannelInfo def test_filter_fields(): + """Test to verify the filtering of channel information fields. + + This test checks if the `filter_fields` method of the `ChannelInfo` class correctly + filters out unwanted fields from the channel information dictionary based on the provided columns. + """ channel_info = { 'channel_id': '123456', 'channel_name': 'Test Channel', @@ -27,6 +30,11 @@ def test_filter_fields(): def test_channel_ids_from_urls_and_usernames(mocker): + """Test to verify fetching channel IDs from both URLs and usernames. + + This test checks if the `execute` method of the `ChannelInfo` class correctly fetches channel IDs + from a list of URLs and usernames, and then calls the `channels_infos` method with these IDs. + """ urls = ["https://www.youtube.com/@Turicas/featured", "https://www.youtube.com/c/PythonicCaf%C3%A9"] usernames = ["Turicas", "PythonicCafe"] diff --git a/youtool/commands/channel_info.py b/youtool/commands/channel_info.py index fb0944e..09103af 100644 --- a/youtool/commands/channel_info.py +++ b/youtool/commands/channel_info.py @@ -8,8 +8,7 @@ class ChannelInfo(Command): - """ - Get channel info from a list of IDs (or CSV filename with IDs inside), generate CSV output + """Get channel info from a list of IDs (or CSV filename with IDs inside), generate CSV output (same schema for `channel` dicts) """ name = "channel-info" @@ -35,8 +34,7 @@ class ChannelInfo(Command): @staticmethod def filter_fields(channel_info: Dict, info_columns: Optional[List] = None): - """ - Filters the fields of a dictionary containing channel information based on + """Filters the fields of a dictionary containing channel information based on specified columns. Args: @@ -55,8 +53,8 @@ def filter_fields(channel_info: Dict, info_columns: Optional[List] = None): @classmethod def execute(cls: Self, **kwargs) -> str: - """ - Execute the channel-info command to fetch YouTube channel information from URLs or usernames and save them to a CSV file. + """Execute the channel-info command to fetch YouTube channel information from URLs or + usernames and save them to a CSV file. Args: urls (list[str], optional): A list of YouTube channel URLs. If not provided, `urls_file_path` must be specified. @@ -69,7 +67,8 @@ def execute(cls: Self, **kwargs) -> str: Default is "channel_url". username_column_name (str, optional): The name of the column in the `usernames_file_path` CSV file that contains the usernames. Default is "channel_username". - info_columns (str, optional): Comma-separated list of columns to include in the output CSV. Default is the class attribute `INFO_COLUMNS`. + info_columns (str, optional): Comma-separated list of columns to include in the output CSV. + Default is the class attribute `INFO_COLUMNS`. Returns: str: A message indicating the result of the command. If `output_file_path` is specified, the message will @@ -78,6 +77,7 @@ def execute(cls: Self, **kwargs) -> str: Raises: Exception: If neither `urls`, `usernames`, `urls_file_path` nor `usernames_file_path` is provided. """ + urls = kwargs.get("urls") usernames = kwargs.get("usernames") urls_file_path = kwargs.get("urls_file_path") From 923170e226881b2e4dd570fb74e9d3023f5cb344 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 23:16:33 -0300 Subject: [PATCH 44/85] fix --- tests/commands/test_channel_info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/commands/test_channel_info.py b/tests/commands/test_channel_info.py index 5e6ef33..67f5c3d 100644 --- a/tests/commands/test_channel_info.py +++ b/tests/commands/test_channel_info.py @@ -1,3 +1,5 @@ +import pytest + from unittest.mock import Mock, call from youtool.commands.channel_info import ChannelInfo From d77a36bd8aab208f0039365a0ad16307d26c161e Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Sun, 30 Jun 2024 19:29:28 -0300 Subject: [PATCH 45/85] Add new command in list --- youtool/commands/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/youtool/commands/__init__.py b/youtool/commands/__init__.py index 1939a22..51afcfc 100644 --- a/youtool/commands/__init__.py +++ b/youtool/commands/__init__.py @@ -1,12 +1,14 @@ from .base import Command from .channel_id import ChannelId from .channel_info import ChannelInfo +from .video_info import VideoInfo COMMANDS = [ ChannelId, - ChannelInfo + ChannelInfo, + VideoInfo ] __all__ = [ - COMMANDS, ChannelId, ChannelInfo + COMMANDS, ChannelId, ChannelInfo, VideoInfo ] From 734e98129d74479679bb863ebb7b304efed579ce Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Sun, 30 Jun 2024 19:32:20 -0300 Subject: [PATCH 46/85] - Implement CSV input processing for video IDs and URLs in VideoInfo class; --- youtool/commands/video_info.py | 106 +++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 youtool/commands/video_info.py diff --git a/youtool/commands/video_info.py b/youtool/commands/video_info.py new file mode 100644 index 0000000..8fd8eed --- /dev/null +++ b/youtool/commands/video_info.py @@ -0,0 +1,106 @@ +import csv + +from typing import List, Dict, Optional, Self + +from youtool import YouTube + +from .base import Command + + +class VideoInfo(Command): + """Get video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (same schema for video dicts)") + """ + name = "video-info" + arguments = [ + {"name": "--ids", "type": str, "help": "Video IDs", "nargs": "*"}, + {"name": "--urls", "type": str, "help": "Video URLs", "nargs": "*"}, + {"name": "--input-file-path", "type": str, "help": "Input CSV file path with URLs/IDs"}, + {"name": "--output-file-path", "type": str, "help": "Output CSV file path"} + ] + + ID_COLUMN_NAME: str = "video_id" + URL_COLUMN_NAME: str = "video_url" + INFO_COLUMNS: List[str] = [ + "id", "title", "description", "published_at", "view_count", "like_count", "comment_count" + ] + + @staticmethod + def filter_fields(video_info: Dict, info_columns: Optional[List] = None) -> Dict: + """Filters the fields of a dictionary containing video information based on specified columns. + + Args: + video_info (Dict): A dictionary containing video information. + info_columns (Optional[List], optional): A list specifying which fields to include in the filtered output. + If None, returns the entire video_info dictionary. Defaults to None. + + Returns: + A dictionary containing only the fields specified in info_columns (if provided) + or the entire video_info dictionary if info_columns is None. + """ + return { + field: value for field, value in video_info.items() if field in info_columns + } if info_columns else video_info + + @classmethod + def execute(cls: Self, **kwargs) -> str: + """ + Execute the video-info command to fetch YouTube video information from IDs or URLs and save them to a CSV file. + + Args: + ids (list[str], optional): A list of YouTube video IDs. If not provided, input_file_path must be specified. + urls (list[str], optional): A list of YouTube video URLs. If not provided, input_file_path must be specified. + urls_file_path (str, optional): Path to a CSV file containing YouTube channel URLs. + ids_file_path (str, optional): Path to a CSV file containing YouTube channel IDs. + input_file_path (str, optional): Path to a CSV file containing YouTube video URLs or IDs. + output_file_path (str, optional): Path to the output CSV file where video information will be saved. + api_key (str): The API key to authenticate with the YouTube Data API. + url_column_name (str, optional): The name of the column in the input_file_path CSV file that contains the URLs. + Default is "video_url". + id_column_name (str, optional): The name of the column in the input_file_path CSV file that contains the IDs. + Default is "video_id". + info_columns (str, optional): Comma-separated list of columns to include in the output CSV. Default is the class attribute INFO_COLUMNS. + + Returns: + A message indicating the result of the command. If output_file_path is specified, + the message will include the path to the generated CSV file. + Otherwise, it will return the result as a string. + + Raises: + Exception: If neither ids, urls, nor input_file_path is provided. + """ + ids = kwargs.get("ids") + urls = kwargs.get("urls") + input_file_path = kwargs.get("input_file_path") + output_file_path = kwargs.get("output_file_path") + api_key = kwargs.get("api_key") + + info_columns = kwargs.get("info_columns") + + info_columns = [ + column.strip() for column in info_columns.split(",") + ] if info_columns else VideoInfo.INFO_COLUMNS + + if input_file_path: + with open(input_file_path, mode='r') as infile: + reader = csv.DictReader(infile) + for row in reader: + if cls.ID_COLUMN_NAME in row: + ids.append(row[cls.ID_COLUMN_NAME]) + elif cls.URL_COLUMN_NAME in row: + urls.append(row[cls.URL_COLUMN_NAME]) + + if not ids and not urls: + raise Exception("Either 'ids' or 'urls' must be provided for the video-info command") + + youtube = YouTube([api_key], disable_ipv6=True) + + video_infos = list(youtube.videos_infos(ids)) + + return cls.data_to_csv( + data=[ + VideoInfo.filter_fields( + video_info, info_columns + ) for video_info in video_infos + ], + output_file_path=output_file_path + ) From e9000ca074771b4c57c9ec5c0651f71afc44215b Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:21:05 -0300 Subject: [PATCH 47/85] Add updates docstrings --- youtool/commands/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 077c826..275c282 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -44,7 +44,7 @@ def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: parser.set_defaults(func=cls.execute) @classmethod - def execute(cls, **kwargs) -> str: # noqa: D417 + def execute(cls, **kwargs) -> str: """Executes the command. This method should be overridden by subclasses to define the command's behavior. From d4327d1851c36d5f54b6ed574f4dacb0e7591e43 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:22:26 -0300 Subject: [PATCH 48/85] Add updates docstrings --- youtool/commands/channel_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index d42f311..c599982 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -21,7 +21,7 @@ class ChannelId(Command): CHANNEL_ID_COLUMN_NAME: str = "channel_id" @classmethod - def execute(cls, **kwargs) -> str: # noqa: D417 + def execute(cls, **kwargs) -> str: """Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. This method retrieves YouTube channel IDs from a list of provided URLs or from a file containing URLs. From c4134e0dbdabd0adb307da566e8c93b8a0a1510c Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:51:39 -0300 Subject: [PATCH 49/85] - Add updates --- youtool/commands/channel_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index c599982..d42f311 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -21,7 +21,7 @@ class ChannelId(Command): CHANNEL_ID_COLUMN_NAME: str = "channel_id" @classmethod - def execute(cls, **kwargs) -> str: + def execute(cls, **kwargs) -> str: # noqa: D417 """Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. This method retrieves YouTube channel IDs from a list of provided URLs or from a file containing URLs. From 068340369caf920ef6d2ba074193613cca92c2ce Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:53:51 -0300 Subject: [PATCH 50/85] - Add updates --- youtool/commands/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 275c282..077c826 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -44,7 +44,7 @@ def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: parser.set_defaults(func=cls.execute) @classmethod - def execute(cls, **kwargs) -> str: + def execute(cls, **kwargs) -> str: # noqa: D417 """Executes the command. This method should be overridden by subclasses to define the command's behavior. From 60bd1447d79332b595346cedc8a09a5e46bf34d0 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 12:40:16 -0300 Subject: [PATCH 51/85] Add update --- tests/commands/test_base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py index e15c787..90fd955 100644 --- a/tests/commands/test_base.py +++ b/tests/commands/test_base.py @@ -111,7 +111,7 @@ def test_data_from_csv_column_not_found(mock_csv_file): file_path = Path("tests/resources/csv_column_not_found.csv") with pytest.raises(Exception) as exc_info: Command.data_from_csv(file_path, "NonExistentColumn") - assert "Column NonExistentColumn not found on tests/resources/csv_column_not_found.csv" in str(exc_info.value) + assert f"Column NonExistentColumn not found on {file_path}" in str(exc_info.value) @pytest.fixture @@ -170,3 +170,4 @@ def test_data_to_csv_output(tmp_path): result = Command.data_to_csv(data, str(output_file_path)) assert Path(output_file_path).is_file() assert expected_output == Path(output_file_path).read_text() + assert str(output_file_path) == result From 916d6331256e4b665a924ec48dd9e436f3e6f94c Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 13:11:36 -0300 Subject: [PATCH 52/85] add config optional argmuments --- youtool/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/cli.py b/youtool/cli.py index 961d2e6..7a430e5 100644 --- a/youtool/cli.py +++ b/youtool/cli.py @@ -21,7 +21,7 @@ def main(): """ parser = argparse.ArgumentParser(description="CLI Tool for managing YouTube videos add playlists") parser.add_argument("--api-key", type=str, help="YouTube API Key", dest="api_key") - parser.add_argument("--debug", type=bool, help="Debug mode", dest="debug") + parser.add_argument("--debug", default=False, action="store_true", help="Debug mode", dest="debug") subparsers = parser.add_subparsers(required=True, dest="command", title="Command", help="Command to be executed") From 65f44fba5cce5047ff06fe4f09663f05ca53295f Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 13:14:23 -0300 Subject: [PATCH 53/85] add not implemented error --- youtool/commands/video_info.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/youtool/commands/video_info.py b/youtool/commands/video_info.py index 8fd8eed..b375111 100644 --- a/youtool/commands/video_info.py +++ b/youtool/commands/video_info.py @@ -93,14 +93,21 @@ def execute(cls: Self, **kwargs) -> str: raise Exception("Either 'ids' or 'urls' must be provided for the video-info command") youtube = YouTube([api_key], disable_ipv6=True) + + videos_infos = [] + + if ids: + videos_infos += list(youtube.videos_infos(ids)) + if urls: + # TODO: add get videos_infos using urls to youtool + raise NotImplementedError("videos_infos by url not implemented yet") - video_infos = list(youtube.videos_infos(ids)) return cls.data_to_csv( data=[ VideoInfo.filter_fields( video_info, info_columns - ) for video_info in video_infos + ) for video_info in videos_infos ], output_file_path=output_file_path ) From eddfb96c85eacece31cbed338d281bdfa84e5e0a Mon Sep 17 00:00:00 2001 From: Ana Paula Sales Date: Wed, 3 Jul 2024 19:56:25 -0300 Subject: [PATCH 54/85] add video_id_from_url static method --- youtool/commands/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 077c826..414b813 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -5,6 +5,7 @@ from io import StringIO from pathlib import Path from datetime import datetime +from urllib.parse import urlparse, parse_qsl class Command: @@ -17,6 +18,12 @@ class Command: name: str arguments: List[Dict[str, Any]] + @staticmethod + def video_id_from_url(video_url: str) -> Optional[str]: + parsed_url = urlparse(video_url) + parsed_url_query = dict(parse_qsl(parsed_url.query)) + return parsed_url_query.get("v") + @classmethod def generate_parser(cls, subparsers: argparse._SubParsersAction): """Creates a parser for the command and adds it to the subparsers. From b344a72593ebd83b9770cc7f416cb270829f9500 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 20:15:54 -0300 Subject: [PATCH 55/85] add video-info from url case --- youtool/commands/video_info.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/youtool/commands/video_info.py b/youtool/commands/video_info.py index b375111..35ca6a1 100644 --- a/youtool/commands/video_info.py +++ b/youtool/commands/video_info.py @@ -68,8 +68,8 @@ def execute(cls: Self, **kwargs) -> str: Raises: Exception: If neither ids, urls, nor input_file_path is provided. """ - ids = kwargs.get("ids") - urls = kwargs.get("urls") + ids = kwargs.get("ids", []) + urls = kwargs.get("urls", []) input_file_path = kwargs.get("input_file_path") output_file_path = kwargs.get("output_file_path") api_key = kwargs.get("api_key") @@ -93,16 +93,13 @@ def execute(cls: Self, **kwargs) -> str: raise Exception("Either 'ids' or 'urls' must be provided for the video-info command") youtube = YouTube([api_key], disable_ipv6=True) - - videos_infos = [] - if ids: - videos_infos += list(youtube.videos_infos(ids)) if urls: - # TODO: add get videos_infos using urls to youtool - raise NotImplementedError("videos_infos by url not implemented yet") - + ids += [cls.video_id_from_url(url) for url in urls] + # Remove duplicated + ids = list(set(ids)) + videos_infos = list(youtube.videos_infos([_id for _id in ids if _id])) return cls.data_to_csv( data=[ VideoInfo.filter_fields( From 64252d73ab9cda05abacbf7f346a3c66f493fe25 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 13:57:55 -0300 Subject: [PATCH 56/85] - Add test for channel_info command; - Add update channel_info file; - fix test_base --- tests/commands/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py index 90fd955..c87ddea 100644 --- a/tests/commands/test_base.py +++ b/tests/commands/test_base.py @@ -111,7 +111,7 @@ def test_data_from_csv_column_not_found(mock_csv_file): file_path = Path("tests/resources/csv_column_not_found.csv") with pytest.raises(Exception) as exc_info: Command.data_from_csv(file_path, "NonExistentColumn") - assert f"Column NonExistentColumn not found on {file_path}" in str(exc_info.value) + assert "Column NonExistentColumn not found on tests/resources/csv_column_not_found.csv" in str(exc_info.value) @pytest.fixture From 801e5f34c023f16a6c0eae6ebab3d2757bc115fa Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 16:31:54 -0300 Subject: [PATCH 57/85] Add test for video_info command; - Add improvements to base file; - Add changes to test_base file --- tests/commands/test_base.py | 20 ++++++++ tests/commands/test_video_info.py | 80 +++++++++++++++++++++++++++++++ youtool/commands/base.py | 18 +++++++ youtool/commands/video_info.py | 17 ------- 4 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 tests/commands/test_video_info.py diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py index c87ddea..cc26e43 100644 --- a/tests/commands/test_base.py +++ b/tests/commands/test_base.py @@ -171,3 +171,23 @@ def test_data_to_csv_output(tmp_path): assert Path(output_file_path).is_file() assert expected_output == Path(output_file_path).read_text() assert str(output_file_path) == result + +def test_filter_fields(): + channel_info = { + 'channel_id': '123456', + 'channel_name': 'Test Channel', + 'subscribers': 1000, + 'videos': 50, + 'category': 'Tech' + } + + info_columns = ['channel_id', 'channel_name', 'subscribers'] + filtered_info = Command.filter_fields(channel_info, info_columns) + + expected_result = { + 'channel_id': '123456', + 'channel_name': 'Test Channel', + 'subscribers': 1000 + } + + assert filtered_info == expected_result, f"Expected {expected_result}, but got {filtered_info}" \ No newline at end of file diff --git a/tests/commands/test_video_info.py b/tests/commands/test_video_info.py new file mode 100644 index 0000000..49e3168 --- /dev/null +++ b/tests/commands/test_video_info.py @@ -0,0 +1,80 @@ +import csv +import pytest + +from unittest.mock import Mock +from pathlib import Path +from youtool.commands import VideoInfo + + +@pytest.fixture +def youtube_mock(mocker, mock_video_info): + mock = mocker.patch("youtool.commands.video_info.YouTube") + mock_instance = mock.return_value + mock_instance.videos_infos = Mock(return_value=mock_video_info) + return mock_instance + +@pytest.fixture +def mock_video_info(): + return [ + {"id": "tmrhPou85HQ", "title": "Title 1", "description": "Description 1", "published_at": "2021-01-01", "view_count": 100, "like_count": 10, "comment_count": 5}, + {"id": "qoI_x9fylaw", "title": "Title 2", "description": "Description 2", "published_at": "2021-02-01", "view_count": 200, "like_count": 20, "comment_count": 10} + ] + +def test_execute_with_ids_and_urls(youtube_mock, mocker, tmp_path, mock_video_info): + ids = ["tmrhPou85HQ", "qoI_x9fylaw"] + urls = ["https://www.youtube.com/watch?v=tmrhPou85HQ&ab_channel=Turicas", "https://www.youtube.com/watch?v=qoI_x9fylaw&ab_channel=PythonicCaf%C3%A9"] + output_file_path = tmp_path / "output.csv" + + VideoInfo.execute(ids=ids, urls=urls, output_file_path=str(output_file_path), api_key="test_api_key") + + assert Path(output_file_path).is_file() + with open(output_file_path, 'r') as f: + reader = csv.DictReader(f) + csv_data = list(reader) + + assert csv_data[0]["id"] == "tmrhPou85HQ" + assert csv_data[1]["id"] == "qoI_x9fylaw" + +def test_execute_missing_arguments(): + with pytest.raises(Exception) as exc_info: + VideoInfo.execute(api_key="test_api_key") + + assert str(exc_info.value) == "Either 'ids' or 'urls' must be provided for the video-info command" + +def test_execute_with_input_file_path(youtube_mock, mocker, tmp_path, mock_video_info): + input_csv_content = """video_id,video_url + tmrhPou85HQ,https://www.youtube.com/watch?v=tmrhPou85HQ&ab_channel=Turicas + qoI_x9fylaw,https://www.youtube.com/watch?v=qoI_x9fylaw&ab_channel=PythonicCaf%C3%A9 + """ + input_file_path = tmp_path / "input.csv" + output_file_path = tmp_path / "output.csv" + + with open(input_file_path, 'w') as f: + f.write(input_csv_content) + + VideoInfo.execute(input_file_path=str(input_file_path), output_file_path=str(output_file_path), api_key="test_api_key") + + assert Path(output_file_path).is_file() + with open(output_file_path, 'r') as f: + reader = csv.DictReader(f) + csv_data = list(reader) + + assert csv_data[0]["id"] == "tmrhPou85HQ" + assert csv_data[1]["id"] == "qoI_x9fylaw" + + +def test_execute_with_info_columns(youtube_mock, mocker, tmp_path, mock_video_info): + ids = ["tmrhPou85HQ", "qoI_x9fylaw"] + output_file_path = tmp_path / "output.csv" + + VideoInfo.execute(ids=ids, output_file_path=str(output_file_path), api_key="test_api_key", info_columns="id,title") + + assert Path(output_file_path).is_file() + with open(output_file_path, 'r') as f: + reader = csv.DictReader(f) + csv_data = list(reader) + + assert csv_data[0]["id"] == "tmrhPou85HQ" + assert csv_data[0]["title"] == "Title 1" + assert csv_data[1]["id"] == "qoI_x9fylaw" + assert csv_data[1]["title"] == "Title 2" diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 414b813..a2ac387 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -50,6 +50,24 @@ def parse_arguments(cls, subparsers: argparse._SubParsersAction) -> None: parser.add_argument(argument_name, **argument_copy) parser.set_defaults(func=cls.execute) + @staticmethod + def filter_fields(video_info: Dict, info_columns: Optional[List] = None) -> Dict: + """Filters the fields of a dictionary containing video information based on specified columns. + + Args: + video_info (Dict): A dictionary containing video information. + info_columns (Optional[List], optional): A list specifying which fields to include in the filtered output. + If None, returns the entire video_info dictionary. Defaults to None. + + Returns: + A dictionary containing only the fields specified in info_columns (if provided) + or the entire video_info dictionary if info_columns is None. + """ + return { + field: value for field, value in video_info.items() if field in info_columns + } if info_columns else video_info + + @classmethod def execute(cls, **kwargs) -> str: # noqa: D417 """Executes the command. diff --git a/youtool/commands/video_info.py b/youtool/commands/video_info.py index 35ca6a1..f5f344b 100644 --- a/youtool/commands/video_info.py +++ b/youtool/commands/video_info.py @@ -24,23 +24,6 @@ class VideoInfo(Command): "id", "title", "description", "published_at", "view_count", "like_count", "comment_count" ] - @staticmethod - def filter_fields(video_info: Dict, info_columns: Optional[List] = None) -> Dict: - """Filters the fields of a dictionary containing video information based on specified columns. - - Args: - video_info (Dict): A dictionary containing video information. - info_columns (Optional[List], optional): A list specifying which fields to include in the filtered output. - If None, returns the entire video_info dictionary. Defaults to None. - - Returns: - A dictionary containing only the fields specified in info_columns (if provided) - or the entire video_info dictionary if info_columns is None. - """ - return { - field: value for field, value in video_info.items() if field in info_columns - } if info_columns else video_info - @classmethod def execute(cls: Self, **kwargs) -> str: """ From 9572aedffd0f12e9b5a4dc984ad1fbb21bee913c Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 16:48:57 -0300 Subject: [PATCH 58/85] fix --- tests/commands/test_video_info.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/commands/test_video_info.py b/tests/commands/test_video_info.py index 49e3168..ed01c35 100644 --- a/tests/commands/test_video_info.py +++ b/tests/commands/test_video_info.py @@ -15,10 +15,10 @@ def youtube_mock(mocker, mock_video_info): @pytest.fixture def mock_video_info(): - return [ - {"id": "tmrhPou85HQ", "title": "Title 1", "description": "Description 1", "published_at": "2021-01-01", "view_count": 100, "like_count": 10, "comment_count": 5}, - {"id": "qoI_x9fylaw", "title": "Title 2", "description": "Description 2", "published_at": "2021-02-01", "view_count": 200, "like_count": 20, "comment_count": 10} - ] + return [ + {"id": "tmrhPou85HQ", "title": "Title 1", "description": "Description 1", "published_at": "2021-01-01", "view_count": 100, "like_count": 10, "comment_count": 5}, + {"id": "qoI_x9fylaw", "title": "Title 2", "description": "Description 2", "published_at": "2021-02-01", "view_count": 200, "like_count": 20, "comment_count": 10} + ] def test_execute_with_ids_and_urls(youtube_mock, mocker, tmp_path, mock_video_info): ids = ["tmrhPou85HQ", "qoI_x9fylaw"] From 5de983d5afb357640d79c8ea1c071df52cacdccd Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 15:48:36 -0300 Subject: [PATCH 59/85] add docstrings --- tests/commands/test_video_info.py | 26 ++++++++++++++++++++++++++ youtool/commands/video_info.py | 13 ++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/tests/commands/test_video_info.py b/tests/commands/test_video_info.py index ed01c35..f4da48f 100644 --- a/tests/commands/test_video_info.py +++ b/tests/commands/test_video_info.py @@ -8,6 +8,7 @@ @pytest.fixture def youtube_mock(mocker, mock_video_info): + """Fixture to mock the YouTube instance and its videos_infos method.""" mock = mocker.patch("youtool.commands.video_info.YouTube") mock_instance = mock.return_value mock_instance.videos_infos = Mock(return_value=mock_video_info) @@ -15,12 +16,18 @@ def youtube_mock(mocker, mock_video_info): @pytest.fixture def mock_video_info(): + """Fixture to return mock video information.""" return [ {"id": "tmrhPou85HQ", "title": "Title 1", "description": "Description 1", "published_at": "2021-01-01", "view_count": 100, "like_count": 10, "comment_count": 5}, {"id": "qoI_x9fylaw", "title": "Title 2", "description": "Description 2", "published_at": "2021-02-01", "view_count": 200, "like_count": 20, "comment_count": 10} ] def test_execute_with_ids_and_urls(youtube_mock, mocker, tmp_path, mock_video_info): + """Test the execute method with provided video IDs and URLs. + + This test verifies that the execute method can handle both video IDs and URLs, + and correctly writes the video information to the output CSV file. + """ ids = ["tmrhPou85HQ", "qoI_x9fylaw"] urls = ["https://www.youtube.com/watch?v=tmrhPou85HQ&ab_channel=Turicas", "https://www.youtube.com/watch?v=qoI_x9fylaw&ab_channel=PythonicCaf%C3%A9"] output_file_path = tmp_path / "output.csv" @@ -36,12 +43,25 @@ def test_execute_with_ids_and_urls(youtube_mock, mocker, tmp_path, mock_video_in assert csv_data[1]["id"] == "qoI_x9fylaw" def test_execute_missing_arguments(): + """Test the execute method raises an exception when missing required arguments. + + This test verifies that the execute method raises an exception if neither + video IDs nor URLs are provided. + + Raises: + Exception: If neither 'ids' nor 'urls' is provided. + """ with pytest.raises(Exception) as exc_info: VideoInfo.execute(api_key="test_api_key") assert str(exc_info.value) == "Either 'ids' or 'urls' must be provided for the video-info command" def test_execute_with_input_file_path(youtube_mock, mocker, tmp_path, mock_video_info): + """Test the execute method with an input CSV file containing video URLs and IDs. + + This test verifies that the execute method can read video URLs and IDs from + an input CSV file and correctly writes the video information to the output CSV file. + """ input_csv_content = """video_id,video_url tmrhPou85HQ,https://www.youtube.com/watch?v=tmrhPou85HQ&ab_channel=Turicas qoI_x9fylaw,https://www.youtube.com/watch?v=qoI_x9fylaw&ab_channel=PythonicCaf%C3%A9 @@ -64,6 +84,12 @@ def test_execute_with_input_file_path(youtube_mock, mocker, tmp_path, mock_video def test_execute_with_info_columns(youtube_mock, mocker, tmp_path, mock_video_info): + """Test the execute method with specified info columns. + + This test verifies that the execute method can filter the video information + based on specified columns and correctly writes the filtered information + to the output CSV file. + """ ids = ["tmrhPou85HQ", "qoI_x9fylaw"] output_file_path = tmp_path / "output.csv" diff --git a/youtool/commands/video_info.py b/youtool/commands/video_info.py index f5f344b..bfa6534 100644 --- a/youtool/commands/video_info.py +++ b/youtool/commands/video_info.py @@ -32,25 +32,24 @@ def execute(cls: Self, **kwargs) -> str: Args: ids (list[str], optional): A list of YouTube video IDs. If not provided, input_file_path must be specified. urls (list[str], optional): A list of YouTube video URLs. If not provided, input_file_path must be specified. - urls_file_path (str, optional): Path to a CSV file containing YouTube channel URLs. - ids_file_path (str, optional): Path to a CSV file containing YouTube channel IDs. input_file_path (str, optional): Path to a CSV file containing YouTube video URLs or IDs. output_file_path (str, optional): Path to the output CSV file where video information will be saved. api_key (str): The API key to authenticate with the YouTube Data API. url_column_name (str, optional): The name of the column in the input_file_path CSV file that contains the URLs. - Default is "video_url". + Default is "video_url". id_column_name (str, optional): The name of the column in the input_file_path CSV file that contains the IDs. Default is "video_id". - info_columns (str, optional): Comma-separated list of columns to include in the output CSV. Default is the class attribute INFO_COLUMNS. + info_columns (str, optional): Comma-separated list of columns to include in the output CSV. + Default is the class attribute INFO_COLUMNS. Returns: - A message indicating the result of the command. If output_file_path is specified, - the message will include the path to the generated CSV file. - Otherwise, it will return the result as a string. + str: A message indicating the result of the command. If output_file_path is specified, the message will + include the path to the generated CSV file. Otherwise, it will return the result as a string. Raises: Exception: If neither ids, urls, nor input_file_path is provided. """ + ids = kwargs.get("ids", []) urls = kwargs.get("urls", []) input_file_path = kwargs.get("input_file_path") From e8ab076bfbb14ac39a4abf29c9eef29a314c10ee Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:22:26 -0300 Subject: [PATCH 60/85] Add updates docstrings --- youtool/commands/channel_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index d42f311..c599982 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -21,7 +21,7 @@ class ChannelId(Command): CHANNEL_ID_COLUMN_NAME: str = "channel_id" @classmethod - def execute(cls, **kwargs) -> str: # noqa: D417 + def execute(cls, **kwargs) -> str: """Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. This method retrieves YouTube channel IDs from a list of provided URLs or from a file containing URLs. From cac8aae4eea11d68f57b48fbeb887e3d6026b754 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 27 Jun 2024 22:51:39 -0300 Subject: [PATCH 61/85] - Add updates --- youtool/commands/channel_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/channel_id.py b/youtool/commands/channel_id.py index c599982..d42f311 100644 --- a/youtool/commands/channel_id.py +++ b/youtool/commands/channel_id.py @@ -21,7 +21,7 @@ class ChannelId(Command): CHANNEL_ID_COLUMN_NAME: str = "channel_id" @classmethod - def execute(cls, **kwargs) -> str: + def execute(cls, **kwargs) -> str: # noqa: D417 """Execute the channel-id command to fetch YouTube channel IDs from URLs and save them to a CSV file. This method retrieves YouTube channel IDs from a list of provided URLs or from a file containing URLs. From 797b4cb8eacaa076e36aed086c9001fe1ca39085 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 00:59:00 -0300 Subject: [PATCH 62/85] Add test for base file --- tests/commands/test_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py index cc26e43..0f15a64 100644 --- a/tests/commands/test_base.py +++ b/tests/commands/test_base.py @@ -2,6 +2,8 @@ import argparse import pytest +from io import StringIO +from datetime import datetime from pathlib import Path from unittest.mock import MagicMock, patch, mock_open from youtool.commands import Command @@ -190,4 +192,4 @@ def test_filter_fields(): 'subscribers': 1000 } - assert filtered_info == expected_result, f"Expected {expected_result}, but got {filtered_info}" \ No newline at end of file + assert filtered_info == expected_result, f"Expected {expected_result}, but got {filtered_info}" From 3061ca8aa2c31b0c9ead09ee112dde696fa0205f Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 12:40:16 -0300 Subject: [PATCH 63/85] Add update --- tests/commands/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py index 0f15a64..04194b6 100644 --- a/tests/commands/test_base.py +++ b/tests/commands/test_base.py @@ -113,7 +113,7 @@ def test_data_from_csv_column_not_found(mock_csv_file): file_path = Path("tests/resources/csv_column_not_found.csv") with pytest.raises(Exception) as exc_info: Command.data_from_csv(file_path, "NonExistentColumn") - assert "Column NonExistentColumn not found on tests/resources/csv_column_not_found.csv" in str(exc_info.value) + assert f"Column NonExistentColumn not found on {file_path}" in str(exc_info.value) @pytest.fixture From ca3edc14dad19e295c08585a13d8483bbf6d403a Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Tue, 2 Jul 2024 23:41:22 -0300 Subject: [PATCH 64/85] Add video_search command --- youtool/commands/video_search.py | 107 +++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 youtool/commands/video_search.py diff --git a/youtool/commands/video_search.py b/youtool/commands/video_search.py new file mode 100644 index 0000000..ce1d0fa --- /dev/null +++ b/youtool/commands/video_search.py @@ -0,0 +1,107 @@ +import csv + +from typing import List, Dict, Optional, Self + +from youtool import YouTube + +from .base import Command + + +class VideoSearch(Command): + """Search video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (simplified video dict schema or option to get full video info) + """ + name = "video-search" + arguments = [ + {"name": "--ids", "type": str, "help": "Video IDs", "nargs": "*"}, + {"name": "--urls", "type": str, "help": "Video URLs", "nargs": "*"}, + {"name": "--input-file-path", "type": str, "help": "Input CSV file path with URLs/IDs"}, + {"name": "--output-file-path", "type": str, "help": "Output CSV file path"}, + {"name": "--full-info", "type": bool, "help": "Option to get full video info", "default": False} + ] + + ID_COLUMN_NAME: str = "video_id" + URL_COLUMN_NAME: str = "video_url" + INFO_COLUMNS: List[str] = [ + "id", "title", "published_at", "view_count" + ] + FULL_INFO_COLUMNS: List[str] = [ + "id", "title", "description", "published_at", "view_count", "like_count", "comment_count" + ] + + @staticmethod + def filter_fields(video_info: Dict, info_columns: Optional[List] = None) -> Dict: + """Filters the fields of a dictionary containing video information based on specified columns. + + Args: + video_info (Dict): A dictionary containing video information. + info_columns (Optional[List], optional): A list specifying which fields to include in the filtered output. + If None, returns the entire video_info dictionary. Defaults to None. + + Returns: + A dictionary containing only the fields specified in info_columns (if provided) + or the entire video_info dictionary if info_columns is None. + """ + return { + field: value for field, value in video_info.items() if field in info_columns + } if info_columns else video_info + + @classmethod + def execute(cls: Self, **kwargs) -> str: + """ + Execute the video-search command to fetch YouTube video information from IDs or URLs and save them to a CSV file. + + Args: + ids (list[str], optional): A list of YouTube video IDs. If not provided, input_file_path must be specified. + urls (list[str], optional): A list of YouTube video URLs. If not provided, input_file_path must be specified. + input_file_path (str, optional): Path to a CSV file containing YouTube video URLs or IDs. + output_file_path (str, optional): Path to the output CSV file where video information will be saved. + api_key (str): The API key to authenticate with the YouTube Data API. + full_info (bool, optional): Flag to indicate whether to get full video info. Default is False. + + Returns: + A message indicating the result of the command. If output_file_path is specified, + the message will include the path to the generated CSV file. + Otherwise, it will return the result as a string. + + Raises: + Exception: If neither ids, urls, nor input_file_path is provided. + """ + ids = kwargs.get("ids") + urls = kwargs.get("urls") + input_file_path = kwargs.get("input_file_path") + output_file_path = kwargs.get("output_file_path") + api_key = kwargs.get("api_key") + full_info = kwargs.get("full_info", False) + + info_columns = VideoSearch.FULL_INFO_COLUMNS if full_info else VideoSearch.SIMPLE_INFO_COLUMNS + + if input_file_path: + with open(input_file_path, mode='r') as infile: + reader = csv.DictReader(infile) + for row in reader: + if cls.ID_COLUMN_NAME in row and row[cls.ID_COLUMN_NAME]: + ids.append(row[cls.ID_COLUMN_NAME]) + elif cls.URL_COLUMN_NAME in row and row[cls.URL_COLUMN_NAME]: + urls.append(row[cls.URL_COLUMN_NAME]) + + if not ids and not urls: + raise Exception("Either 'ids' or 'urls' must be provided for the video-search command") + + youtube = YouTube([api_key], disable_ipv6=True) + + videos_infos = [] + + if ids: + videos_infos += list(youtube.videos_infos(ids)) + if urls: + # TODO: add get videos_infos using urls to youtool + raise NotImplementedError("videos_infos by url not implemented yet") + + return cls.data_to_csv( + data=[ + VideoSearch.filter_fields( + video_info, info_columns + ) for video_info in videos_infos + ], + output_file_path=output_file_path + ) From fb6391e32ec9cfec1a24d94b3346955f089f3b4d Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 15:28:21 -0300 Subject: [PATCH 65/85] Fix --- youtool/commands/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/youtool/commands/__init__.py b/youtool/commands/__init__.py index 51afcfc..222d48e 100644 --- a/youtool/commands/__init__.py +++ b/youtool/commands/__init__.py @@ -2,13 +2,15 @@ from .channel_id import ChannelId from .channel_info import ChannelInfo from .video_info import VideoInfo +from .video_search import VideoSearch COMMANDS = [ ChannelId, ChannelInfo, - VideoInfo + VideoInfo, + VideoSearch ] __all__ = [ - COMMANDS, ChannelId, ChannelInfo, VideoInfo + "Command", "COMMANDS", "ChannelId", "ChannelInfo", "VideoInfo", "VideoSearch" ] From 301a2e00edc072ac741c8cfbb10f09f2bce51591 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 15:28:55 -0300 Subject: [PATCH 66/85] Add update --- youtool/commands/video_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtool/commands/video_search.py b/youtool/commands/video_search.py index ce1d0fa..390ce04 100644 --- a/youtool/commands/video_search.py +++ b/youtool/commands/video_search.py @@ -73,7 +73,7 @@ def execute(cls: Self, **kwargs) -> str: api_key = kwargs.get("api_key") full_info = kwargs.get("full_info", False) - info_columns = VideoSearch.FULL_INFO_COLUMNS if full_info else VideoSearch.SIMPLE_INFO_COLUMNS + info_columns = VideoSearch.FULL_INFO_COLUMNS if full_info else VideoSearch.INFO_COLUMNS if input_file_path: with open(input_file_path, mode='r') as infile: From 68d5ea5bacdb3ee181501710969dfd788b61d770 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 20:23:32 -0300 Subject: [PATCH 67/85] add video-search from url case --- youtool/commands/video_search.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/youtool/commands/video_search.py b/youtool/commands/video_search.py index 390ce04..db49ea7 100644 --- a/youtool/commands/video_search.py +++ b/youtool/commands/video_search.py @@ -66,8 +66,8 @@ def execute(cls: Self, **kwargs) -> str: Raises: Exception: If neither ids, urls, nor input_file_path is provided. """ - ids = kwargs.get("ids") - urls = kwargs.get("urls") + ids = kwargs.get("ids", []) + urls = kwargs.get("urls", []) input_file_path = kwargs.get("input_file_path") output_file_path = kwargs.get("output_file_path") api_key = kwargs.get("api_key") @@ -89,13 +89,12 @@ def execute(cls: Self, **kwargs) -> str: youtube = YouTube([api_key], disable_ipv6=True) - videos_infos = [] - - if ids: - videos_infos += list(youtube.videos_infos(ids)) if urls: - # TODO: add get videos_infos using urls to youtool - raise NotImplementedError("videos_infos by url not implemented yet") + ids += [cls.video_id_from_url(url) for url in urls] + + # Remove duplicated + ids = list(set(ids)) + videos_infos = list(youtube.videos_infos([_id for _id in ids if _id])) return cls.data_to_csv( data=[ From 6ab1762d3bb04081f34fc8274dc0fc2f853ce24a Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 13:57:55 -0300 Subject: [PATCH 68/85] - Add test for channel_info command; - Add update channel_info file; - fix test_base --- tests/commands/test_base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/commands/test_base.py b/tests/commands/test_base.py index 04194b6..afbcf06 100644 --- a/tests/commands/test_base.py +++ b/tests/commands/test_base.py @@ -2,8 +2,6 @@ import argparse import pytest -from io import StringIO -from datetime import datetime from pathlib import Path from unittest.mock import MagicMock, patch, mock_open from youtool.commands import Command From 937ad3dc0b7b29c0fb01e3d2cc57f786940e689a Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 21:32:42 -0300 Subject: [PATCH 69/85] - Add test for video_search command; - Add updates to some test files; - created conftest file --- tests/commands/conftest.py | 29 +++++++++++++ tests/commands/test_channel_info.py | 4 +- tests/commands/test_video_search.py | 66 +++++++++++++++++++++++++++++ youtool/commands/base.py | 2 +- youtool/commands/video_search.py | 44 +++++++------------ 5 files changed, 113 insertions(+), 32 deletions(-) create mode 100644 tests/commands/conftest.py create mode 100644 tests/commands/test_video_search.py diff --git a/tests/commands/conftest.py b/tests/commands/conftest.py new file mode 100644 index 0000000..9970eab --- /dev/null +++ b/tests/commands/conftest.py @@ -0,0 +1,29 @@ +import pytest + + +@pytest.fixture +def channels_urls(): + return [ + "https://www.youtube.com/@Turicas/featured", + "https://www.youtube.com/c/PythonicCaf%C3%A9" + ] + + +@pytest.fixture +def videos_ids(): + return [ + "video_id_1", + "video_id_2" + ] + + +@pytest.fixture +def videos_urls(videos_ids): + return [ + f"https://www.youtube.com/?v={video_id}" for video_id in videos_ids + ] + + +@pytest.fixture +def usernames(): + return ["Turicas", "PythonicCafe"] diff --git a/tests/commands/test_channel_info.py b/tests/commands/test_channel_info.py index 67f5c3d..55c17d1 100644 --- a/tests/commands/test_channel_info.py +++ b/tests/commands/test_channel_info.py @@ -52,10 +52,10 @@ def test_channel_ids_from_urls_and_usernames(mocker): youtube_mock.return_value.channel_id_from_username = channel_id_from_username_mock youtube_mock.return_value.channels_infos = channels_infos_mock - ChannelInfo.execute(urls=urls, usernames=usernames) + ChannelInfo.execute(urls=channels_urls, usernames=usernames) channel_id_from_url_mock.assert_has_calls( - [call(url) for url in urls] + [call(url) for url in channels_urls] ) channel_id_from_username_mock.assert_has_calls( [call(username) for username in usernames] diff --git a/tests/commands/test_video_search.py b/tests/commands/test_video_search.py new file mode 100644 index 0000000..feeb7ef --- /dev/null +++ b/tests/commands/test_video_search.py @@ -0,0 +1,66 @@ +import csv +import pytest + +from io import StringIO +from unittest.mock import Mock, call +from datetime import datetime + +from youtool.commands.video_search import VideoSearch + + +def test_video_search_string_output(mocker, videos_ids, videos_urls): + youtube_mock = mocker.patch("youtool.commands.video_search.YouTube") + expected_videos_infos = [ + { + column: f"v_{index}" for column in VideoSearch.INFO_COLUMNS + } for index, _ in enumerate(videos_ids) + ] + + csv_file = StringIO() + csv_writer = csv.DictWriter(csv_file, fieldnames=VideoSearch.INFO_COLUMNS) + csv_writer.writeheader() + csv_writer.writerows(expected_videos_infos) + + videos_infos_mock = Mock(return_value=expected_videos_infos) + youtube_mock.return_value.videos_infos = videos_infos_mock + + result = VideoSearch.execute(ids=videos_ids, urls=videos_urls) + + videos_infos_mock.assert_called_once_with(list(set(videos_ids))) + assert result == csv_file.getvalue() + + +def test_video_search_file_output(mocker, videos_ids, videos_urls, tmp_path): + youtube_mock = mocker.patch("youtool.commands.video_search.YouTube") + expected_videos_infos = [ + { + column: f"v_{index}" for column in VideoSearch.INFO_COLUMNS + } for index, _ in enumerate(videos_ids) + ] + + expected_csv_file = StringIO() + csv_writer = csv.DictWriter(expected_csv_file, fieldnames=VideoSearch.INFO_COLUMNS) + csv_writer.writeheader() + csv_writer.writerows(expected_videos_infos) + + timestamp = datetime.now().strftime("%f") + output_file_name = f"output_{timestamp}.csv" + output_file_path = tmp_path / output_file_name + + videos_infos_mock = Mock(return_value=expected_videos_infos) + youtube_mock.return_value.videos_infos = videos_infos_mock + + result_file_path = VideoSearch.execute( + ids=videos_ids, urls=videos_urls, output_file_path=output_file_path + ) + + with open(result_file_path, "r") as result_csv_file: + result_csv = result_csv_file.read() + + videos_infos_mock.assert_called_once_with(list(set(videos_ids))) + assert result_csv.replace("\r", "") == expected_csv_file.getvalue().replace("\r", "") + + +def test_video_search_no_id_and_url_error(): + with pytest.raises(Exception, match="Either 'ids' or 'urls' must be provided"): + VideoSearch.execute(ids=None, urls=None) diff --git a/youtool/commands/base.py b/youtool/commands/base.py index a2ac387..20e1708 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -105,7 +105,7 @@ def data_from_csv(file_path: Path, data_column_name: Optional[str] = None) -> Li if fieldnames is None: raise ValueError("Fieldnames is None") - + if data_column_name not in fieldnames: raise Exception(f"Column {data_column_name} not found on {file_path}") for row in reader: diff --git a/youtool/commands/video_search.py b/youtool/commands/video_search.py index db49ea7..8864aae 100644 --- a/youtool/commands/video_search.py +++ b/youtool/commands/video_search.py @@ -8,7 +8,9 @@ class VideoSearch(Command): - """Search video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (simplified video dict schema or option to get full video info) + """ + Search video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), + generate CSV output (simplified video dict schema or option to get full video info) """ name = "video-search" arguments = [ @@ -16,7 +18,9 @@ class VideoSearch(Command): {"name": "--urls", "type": str, "help": "Video URLs", "nargs": "*"}, {"name": "--input-file-path", "type": str, "help": "Input CSV file path with URLs/IDs"}, {"name": "--output-file-path", "type": str, "help": "Output CSV file path"}, - {"name": "--full-info", "type": bool, "help": "Option to get full video info", "default": False} + {"name": "--full-info", "type": bool, "help": "Option to get full video info", "default": False}, + {"name": "--url-column-name", "type": str, "help": "URL column name on csv input files"}, + {"name": "--id-column-name", "type": str, "help": "Channel ID column name on csv output files"} ] ID_COLUMN_NAME: str = "video_id" @@ -28,23 +32,6 @@ class VideoSearch(Command): "id", "title", "description", "published_at", "view_count", "like_count", "comment_count" ] - @staticmethod - def filter_fields(video_info: Dict, info_columns: Optional[List] = None) -> Dict: - """Filters the fields of a dictionary containing video information based on specified columns. - - Args: - video_info (Dict): A dictionary containing video information. - info_columns (Optional[List], optional): A list specifying which fields to include in the filtered output. - If None, returns the entire video_info dictionary. Defaults to None. - - Returns: - A dictionary containing only the fields specified in info_columns (if provided) - or the entire video_info dictionary if info_columns is None. - """ - return { - field: value for field, value in video_info.items() if field in info_columns - } if info_columns else video_info - @classmethod def execute(cls: Self, **kwargs) -> str: """ @@ -68,21 +55,20 @@ def execute(cls: Self, **kwargs) -> str: """ ids = kwargs.get("ids", []) urls = kwargs.get("urls", []) - input_file_path = kwargs.get("input_file_path") output_file_path = kwargs.get("output_file_path") api_key = kwargs.get("api_key") full_info = kwargs.get("full_info", False) + url_column_name = kwargs.get("url_column_name", cls.URL_COLUMN_NAME) + id_column_name = kwargs.get("id_column_name", cls.ID_COLUMN_NAME) + info_columns = VideoSearch.FULL_INFO_COLUMNS if full_info else VideoSearch.INFO_COLUMNS - if input_file_path: - with open(input_file_path, mode='r') as infile: - reader = csv.DictReader(infile) - for row in reader: - if cls.ID_COLUMN_NAME in row and row[cls.ID_COLUMN_NAME]: - ids.append(row[cls.ID_COLUMN_NAME]) - elif cls.URL_COLUMN_NAME in row and row[cls.URL_COLUMN_NAME]: - urls.append(row[cls.URL_COLUMN_NAME]) + if (input_file_path := kwargs.get("input_file_path")): + if (urls_from_csv := cls.data_from_csv(input_file_path, url_column_name)): + ids += [cls.video_id_from_url(url) for url in urls_from_csv] + if (ids_from_csv := cls.data_from_csv(input_file_path, id_column_name)): + ids += ids_from_csv if not ids and not urls: raise Exception("Either 'ids' or 'urls' must be provided for the video-search command") @@ -95,7 +81,7 @@ def execute(cls: Self, **kwargs) -> str: # Remove duplicated ids = list(set(ids)) videos_infos = list(youtube.videos_infos([_id for _id in ids if _id])) - + return cls.data_to_csv( data=[ VideoSearch.filter_fields( From 00a30972113a5251ea62720eeb72c5fcfde85721 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 15:58:47 -0300 Subject: [PATCH 70/85] add docstrings --- tests/commands/test_video_search.py | 20 +++++++++++++++++++- youtool/commands/video_search.py | 4 +++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/commands/test_video_search.py b/tests/commands/test_video_search.py index feeb7ef..8b0572c 100644 --- a/tests/commands/test_video_search.py +++ b/tests/commands/test_video_search.py @@ -2,13 +2,18 @@ import pytest from io import StringIO -from unittest.mock import Mock, call +from unittest.mock import Mock from datetime import datetime from youtool.commands.video_search import VideoSearch def test_video_search_string_output(mocker, videos_ids, videos_urls): + """Test the execution of the video-search command and verify the output as string. + + This test simulates the execution of the `VideoSearch.execute` command with a list of video IDs and URLs, + and checks if the output is correctly formatted as a CSV string. + """ youtube_mock = mocker.patch("youtool.commands.video_search.YouTube") expected_videos_infos = [ { @@ -31,6 +36,11 @@ def test_video_search_string_output(mocker, videos_ids, videos_urls): def test_video_search_file_output(mocker, videos_ids, videos_urls, tmp_path): + """Test the execution of the video-search command and verify the output to a file. + + This test simulates the execution of the `VideoSearch.execute` command with a list of video IDs and URLs, + and checks if the output is correctly written to a CSV file. + """ youtube_mock = mocker.patch("youtool.commands.video_search.YouTube") expected_videos_infos = [ { @@ -62,5 +72,13 @@ def test_video_search_file_output(mocker, videos_ids, videos_urls, tmp_path): def test_video_search_no_id_and_url_error(): + """Test if the video-search command raises an exception when neither IDs nor URLs are provided. + + This test checks if executing the `VideoSearch.execute` command without providing IDs or URLs + raises the expected exception. + + Assertions: + - Assert that the raised exception matches the expected error message. + """ with pytest.raises(Exception, match="Either 'ids' or 'urls' must be provided"): VideoSearch.execute(ids=None, urls=None) diff --git a/youtool/commands/video_search.py b/youtool/commands/video_search.py index 8864aae..4713a84 100644 --- a/youtool/commands/video_search.py +++ b/youtool/commands/video_search.py @@ -44,9 +44,11 @@ def execute(cls: Self, **kwargs) -> str: output_file_path (str, optional): Path to the output CSV file where video information will be saved. api_key (str): The API key to authenticate with the YouTube Data API. full_info (bool, optional): Flag to indicate whether to get full video info. Default is False. + url_column_name (str, optional): The name of the column in the input CSV file that contains the URLs. Default is "video_url". + id_column_name (str, optional): The name of the column in the input CSV file that contains the IDs. Default is "video_id". Returns: - A message indicating the result of the command. If output_file_path is specified, + str: A message indicating the result of the command. If output_file_path is specified, the message will include the path to the generated CSV file. Otherwise, it will return the result as a string. From 0552a621490776927e33b84238debd01b5559a40 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 23:01:10 -0300 Subject: [PATCH 71/85] add updates channel_info test --- tests/commands/test_channel_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/commands/test_channel_info.py b/tests/commands/test_channel_info.py index 55c17d1..62623e8 100644 --- a/tests/commands/test_channel_info.py +++ b/tests/commands/test_channel_info.py @@ -31,7 +31,7 @@ def test_filter_fields(): assert filtered_info == expected_result, f"Expected {expected_result}, but got {filtered_info}" -def test_channel_ids_from_urls_and_usernames(mocker): +def test_channel_ids_from_urls_and_usernames(mocker, channels_urls): """Test to verify fetching channel IDs from both URLs and usernames. This test checks if the `execute` method of the `ChannelInfo` class correctly fetches channel IDs From 4dc8f34796a2b28806a7a7d91356affb08a3f9f0 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 00:06:54 -0300 Subject: [PATCH 72/85] Remove unnecessary comment --- youtool/cli.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/youtool/cli.py b/youtool/cli.py index 7a430e5..28b055c 100644 --- a/youtool/cli.py +++ b/youtool/cli.py @@ -25,8 +25,6 @@ def main(): subparsers = parser.add_subparsers(required=True, dest="command", title="Command", help="Command to be executed") - # cmd_video_search = subparsers.add_parser("video-search", help="Get video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (simplified `video` dict schema or option to get full video info after)") - # cmd_video_comments = subparsers.add_parser("video-comments", help="Get comments from a video ID, generate CSV output (same schema for `comment` dicts)") # cmd_video_livechat = subparsers.add_parser("video-livechat", help="Get comments from a video ID, generate CSV output (same schema for `chat_message` dicts)") # cmd_video_transcriptions = subparsers.add_parser("video-transcription", help="Download video transcriptions based on language code, path and list of video IDs or URLs (or CSV filename with URLs/IDs inside), download files to destination and report results") From cfa05328904ac7966f372ac40f26937f37135fa7 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 00:07:41 -0300 Subject: [PATCH 73/85] Add video_comments command --- youtool/commands/video_comments.py | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 youtool/commands/video_comments.py diff --git a/youtool/commands/video_comments.py b/youtool/commands/video_comments.py new file mode 100644 index 0000000..ec07e18 --- /dev/null +++ b/youtool/commands/video_comments.py @@ -0,0 +1,47 @@ +import csv +from typing import List, Dict, Optional, Self + +from youtool import YouTube +from .base import Command + +class VideoComments(Command): + """Get comments from a video ID, generate CSV output (same schema for comment dicts)""" + + name = "video-comments" + arguments = [ + {"name": "--id", "type": str, "help": "Video ID", "required": True}, + {"name": "--output-file-path", "type": str, "help": "Output CSV file path"} + ] + + COMMENT_COLUMNS: List[str] = [ + "comment_id", "author_display_name", "text_display", "like_count", "published_at" + ] + + @classmethod + def execute(cls: Self, **kwargs) -> str: + """ + Execute the get-comments command to fetch comments from a YouTube video and save them to a CSV file. + + Args: + id (str): The ID of the YouTube video. + output_file_path (str): Path to the output CSV file where comments will be saved. + api_key (str): The API key to authenticate with the YouTube Data API. + + Returns: + A message indicating the result of the command. If output_file_path is specified, + the message will include the path to the generated CSV file. + Otherwise, it will return the result as a string. + """ + video_id = kwargs.get("id") + output_file_path = kwargs.get("output_file_path") + api_key = kwargs.get("api_key") + + youtube = YouTube([api_key], disable_ipv6=True) + + comments = list(youtube.video_comments(video_id)) + + return cls.data_to_csv( + data=comments, + output_file_path=output_file_path + ) + \ No newline at end of file From 221770cbc1f440a9c071f04433e6ab632b167a13 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 16:26:02 -0300 Subject: [PATCH 74/85] Add update --- youtool/commands/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/youtool/commands/__init__.py b/youtool/commands/__init__.py index 222d48e..72913ce 100644 --- a/youtool/commands/__init__.py +++ b/youtool/commands/__init__.py @@ -3,14 +3,16 @@ from .channel_info import ChannelInfo from .video_info import VideoInfo from .video_search import VideoSearch +from .video_comments import VideoComments COMMANDS = [ ChannelId, ChannelInfo, VideoInfo, - VideoSearch + VideoSearch, + VideoComments ] __all__ = [ - "Command", "COMMANDS", "ChannelId", "ChannelInfo", "VideoInfo", "VideoSearch" + "Command", "COMMANDS", "ChannelId", "ChannelInfo", "VideoInfo", "VideoSearch", "VideoComments" ] From cfcccbcb3c57b288fefc53d6956470b05beac347 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 21:32:42 -0300 Subject: [PATCH 75/85] - Add test for video_search command; - Add updates to some test files; - created conftest file --- tests/commands/test_video_search.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/commands/test_video_search.py b/tests/commands/test_video_search.py index 8b0572c..912bcde 100644 --- a/tests/commands/test_video_search.py +++ b/tests/commands/test_video_search.py @@ -2,18 +2,25 @@ import pytest from io import StringIO +<<<<<<< HEAD from unittest.mock import Mock +======= +from unittest.mock import Mock, call +>>>>>>> 0e02e77 (- Add test for video_search command;) from datetime import datetime from youtool.commands.video_search import VideoSearch def test_video_search_string_output(mocker, videos_ids, videos_urls): +<<<<<<< HEAD """Test the execution of the video-search command and verify the output as string. This test simulates the execution of the `VideoSearch.execute` command with a list of video IDs and URLs, and checks if the output is correctly formatted as a CSV string. """ +======= +>>>>>>> 0e02e77 (- Add test for video_search command;) youtube_mock = mocker.patch("youtool.commands.video_search.YouTube") expected_videos_infos = [ { @@ -36,11 +43,14 @@ def test_video_search_string_output(mocker, videos_ids, videos_urls): def test_video_search_file_output(mocker, videos_ids, videos_urls, tmp_path): +<<<<<<< HEAD """Test the execution of the video-search command and verify the output to a file. This test simulates the execution of the `VideoSearch.execute` command with a list of video IDs and URLs, and checks if the output is correctly written to a CSV file. """ +======= +>>>>>>> 0e02e77 (- Add test for video_search command;) youtube_mock = mocker.patch("youtool.commands.video_search.YouTube") expected_videos_infos = [ { @@ -72,6 +82,7 @@ def test_video_search_file_output(mocker, videos_ids, videos_urls, tmp_path): def test_video_search_no_id_and_url_error(): +<<<<<<< HEAD """Test if the video-search command raises an exception when neither IDs nor URLs are provided. This test checks if executing the `VideoSearch.execute` command without providing IDs or URLs @@ -80,5 +91,7 @@ def test_video_search_no_id_and_url_error(): Assertions: - Assert that the raised exception matches the expected error message. """ +======= +>>>>>>> 0e02e77 (- Add test for video_search command;) with pytest.raises(Exception, match="Either 'ids' or 'urls' must be provided"): VideoSearch.execute(ids=None, urls=None) From 4112d021b7c305004c06cb08e5ce501e7b0608eb Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 22:22:02 -0300 Subject: [PATCH 76/85] - Add test for video_comments command --- tests/commands/test_video_comments.py | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/commands/test_video_comments.py diff --git a/tests/commands/test_video_comments.py b/tests/commands/test_video_comments.py new file mode 100644 index 0000000..fc19164 --- /dev/null +++ b/tests/commands/test_video_comments.py @@ -0,0 +1,59 @@ +import csv +import pytest + +from io import StringIO +from datetime import datetime +from unittest.mock import Mock +from youtool.commands import VideoComments + + +def test_video_comments(mocker): + youtube_mock = mocker.patch("youtool.commands.video_comments.YouTube") + video_id = "video_id_mock" + + expected_result = [ + {"text": "my_comment", "author": "my_name"} + ] + + csv_file = StringIO() + csv_writer = csv.DictWriter(csv_file, fieldnames=expected_result[0].keys()) + csv_writer.writeheader() + csv_writer.writerows(expected_result) + + videos_comments_mock = Mock(return_value=expected_result) + youtube_mock.return_value.video_comments = videos_comments_mock + result = VideoComments.execute(id=video_id) + + videos_comments_mock.assert_called_once_with(video_id) + + assert result == csv_file.getvalue() + + +def test_video_comments_with_file_output(mocker, tmp_path): + youtube_mock = mocker.patch("youtool.commands.video_comments.YouTube") + video_id = "video_id_mock" + + expected_result = [ + {"text": "my_comment", "author": "my_name"} + ] + + csv_file = StringIO() + csv_writer = csv.DictWriter(csv_file, fieldnames=expected_result[0].keys()) + csv_writer.writeheader() + csv_writer.writerows(expected_result) + + timestamp = datetime.now().strftime("%f") + output_file_name = f"output_{timestamp}.csv" + output_file_path = tmp_path / output_file_name + + videos_comments_mock = Mock(return_value=expected_result) + youtube_mock.return_value.video_comments = videos_comments_mock + + result_file_path = VideoComments.execute(id=video_id, output_file_path=output_file_path) + + with open(result_file_path, "r") as result_csv_file: + result_csv = result_csv_file.read() + + videos_comments_mock.assert_called_once_with(video_id) + + assert result_csv.replace("\r", "") == csv_file.getvalue().replace("\r", "") From 59d1ad513309889c4adfb9e0b440447534231aae Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 16:04:55 -0300 Subject: [PATCH 77/85] add docstrings --- tests/commands/test_video_comments.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/commands/test_video_comments.py b/tests/commands/test_video_comments.py index fc19164..386c5de 100644 --- a/tests/commands/test_video_comments.py +++ b/tests/commands/test_video_comments.py @@ -8,6 +8,11 @@ def test_video_comments(mocker): + """Test case for fetching video comments and verifying the output. + + This test mocks the YouTube API to simulate fetching comments for a video, + then compares the generated CSV output with expected comments. + """ youtube_mock = mocker.patch("youtool.commands.video_comments.YouTube") video_id = "video_id_mock" @@ -30,6 +35,11 @@ def test_video_comments(mocker): def test_video_comments_with_file_output(mocker, tmp_path): + """Test case for fetching video comments and saving them to a CSV file. + + This test mocks the YouTube API to simulate fetching comments for a video, + then saves the comments to a temporary CSV file. + """ youtube_mock = mocker.patch("youtool.commands.video_comments.YouTube") video_id = "video_id_mock" From bd78a70468b6f8d77c829830e2e9462077a4fcf8 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 15:15:30 -0300 Subject: [PATCH 78/85] Add video_livechat command --- youtool/commands/video_livechat.py | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 youtool/commands/video_livechat.py diff --git a/youtool/commands/video_livechat.py b/youtool/commands/video_livechat.py new file mode 100644 index 0000000..775b857 --- /dev/null +++ b/youtool/commands/video_livechat.py @@ -0,0 +1,81 @@ +import csv +from typing import List, Dict, Optional, Self +from chat_downloader import ChatDownloader +from chat_downloader.errors import ChatDisabled, LoginRequired, NoChatReplay +from .base import Command +from datetime import datetime + +class VideoLiveChat(Command): + """Get live chat comments from a video ID, generate CSV output (same schema for chat_message dicts)""" + name = "video-livechat" + arguments = [ + {"name": "--id", "type": str, "help": "Video ID", "required": True}, + {"name": "--output-file-path", "type": str, "help": "Output CSV file path"}, + {"name": "--expand-emojis", "type": bool, "help": "Expand emojis in chat messages", "default": True} + ] + + CHAT_COLUMNS: List[str] = [ + "id", "video_id", "created_at", "type", "action", "video_time", + "author", "author_id", "author_image_url", "text", + "money_currency", "money_amount" + ] + + @staticmethod + def parse_timestamp(timestamp: str) -> str: + return datetime.utcfromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S') + + @staticmethod + def parse_decimal(value: Optional[str]) -> Optional[float]: + return float(value.replace(',', '')) if value else None + + @classmethod + def execute(cls: Self, **kwargs) -> str: + """ + Execute the video-livechat command to fetch live chat messages from a YouTube video and save them to a CSV file. + + Args: + id (str): The ID of the YouTube video. + output_file_path (str): Path to the output CSV file where chat messages will be saved. + expand_emojis (bool): Whether to expand emojis in chat messages. Defaults to True. + api_key (str): The API key to authenticate with the YouTube Data API. + + Returns: + A message indicating the result of the command. If output_file_path is specified, + the message will include the path to the generated CSV file. + Otherwise, it will return the result as a string. + """ + video_id = kwargs.get("id") + output_file_path = kwargs.get("output_file_path") + expand_emojis = kwargs.get("expand_emojis", True) + + downloader = ChatDownloader() + video_url = f"https://youtube.com/watch?v={video_id}" + + chat_messages = [] + try: + live = downloader.get_chat(video_url, message_groups=["messages", "superchat"]) + for message in live: + text = message["message"] + if expand_emojis: + for emoji in message.get("emotes", []): + for shortcut in emoji["shortcuts"]: + text = text.replace(shortcut, emoji["id"]) + money = message.get("money", {}) or {} + chat_messages.append({ + "id": message["message_id"], + "video_id": video_id, + "created_at": cls.parse_timestamp(message["timestamp"]), + "type": message["message_type"], + "action": message["action_type"], + "video_time": float(message["time_in_seconds"]), + "author": message["author"]["name"], + "author_id": message["author"]["id"], + "author_image_url": [img for img in message["author"]["images"] if img["id"] == "source"][0]["url"], + "text": text, + "money_currency": money.get("currency"), + "money_amount": cls.parse_decimal(money.get("amount")), + }) + except (LoginRequired, NoChatReplay, ChatDisabled): + raise + + return cls.data_to_csv(chat_messages, output_file_path) From bc966433fe672dab2332de84279e4ae747b911cc Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 21:32:42 -0300 Subject: [PATCH 79/85] - Add test for video_search command; - Add updates to some test files; - created conftest file --- tests/commands/test_video_search.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tests/commands/test_video_search.py b/tests/commands/test_video_search.py index 912bcde..a30a879 100644 --- a/tests/commands/test_video_search.py +++ b/tests/commands/test_video_search.py @@ -2,25 +2,19 @@ import pytest from io import StringIO -<<<<<<< HEAD from unittest.mock import Mock -======= -from unittest.mock import Mock, call ->>>>>>> 0e02e77 (- Add test for video_search command;) + from datetime import datetime from youtool.commands.video_search import VideoSearch def test_video_search_string_output(mocker, videos_ids, videos_urls): -<<<<<<< HEAD """Test the execution of the video-search command and verify the output as string. This test simulates the execution of the `VideoSearch.execute` command with a list of video IDs and URLs, and checks if the output is correctly formatted as a CSV string. """ -======= ->>>>>>> 0e02e77 (- Add test for video_search command;) youtube_mock = mocker.patch("youtool.commands.video_search.YouTube") expected_videos_infos = [ { @@ -43,14 +37,11 @@ def test_video_search_string_output(mocker, videos_ids, videos_urls): def test_video_search_file_output(mocker, videos_ids, videos_urls, tmp_path): -<<<<<<< HEAD """Test the execution of the video-search command and verify the output to a file. This test simulates the execution of the `VideoSearch.execute` command with a list of video IDs and URLs, and checks if the output is correctly written to a CSV file. """ -======= ->>>>>>> 0e02e77 (- Add test for video_search command;) youtube_mock = mocker.patch("youtool.commands.video_search.YouTube") expected_videos_infos = [ { @@ -82,7 +73,6 @@ def test_video_search_file_output(mocker, videos_ids, videos_urls, tmp_path): def test_video_search_no_id_and_url_error(): -<<<<<<< HEAD """Test if the video-search command raises an exception when neither IDs nor URLs are provided. This test checks if executing the `VideoSearch.execute` command without providing IDs or URLs @@ -91,7 +81,6 @@ def test_video_search_no_id_and_url_error(): Assertions: - Assert that the raised exception matches the expected error message. """ -======= ->>>>>>> 0e02e77 (- Add test for video_search command;) + with pytest.raises(Exception, match="Either 'ids' or 'urls' must be provided"): VideoSearch.execute(ids=None, urls=None) From 6db04480509227964f7e20239704cba15dfa1a32 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Thu, 4 Jul 2024 22:32:13 -0300 Subject: [PATCH 80/85] - Add test for video_livechat command --- tests/commands/test_video_livechat.py | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/commands/test_video_livechat.py diff --git a/tests/commands/test_video_livechat.py b/tests/commands/test_video_livechat.py new file mode 100644 index 0000000..6f22ad9 --- /dev/null +++ b/tests/commands/test_video_livechat.py @@ -0,0 +1,59 @@ +import csv +import pytest + +from io import StringIO +from datetime import datetime +from unittest.mock import Mock +from youtool.commands import VideoLiveChat + + +def test_video_livechat(mocker): + youtube_mock = mocker.patch("youtool.commands.video_livechat.YouTube") + video_id = "video_id_mock" + + expected_result = [ + {column: "data" for column in VideoLiveChat.CHAT_MESSAGE_COLUMNS} + ] + + csv_file = StringIO() + csv_writer = csv.DictWriter(csv_file, fieldnames=expected_result[0].keys()) + csv_writer.writeheader() + csv_writer.writerows(expected_result) + + videos_livechat_mock = Mock(return_value=expected_result) + youtube_mock.return_value.video_livechat = videos_livechat_mock + result = VideoLiveChat.execute(id=video_id) + + videos_livechat_mock.assert_called_once_with(video_id) + + assert result == csv_file.getvalue() + + +def test_video_livechat_with_file_output(mocker, tmp_path): + youtube_mock = mocker.patch("youtool.commands.video_livechat.YouTube") + video_id = "video_id_mock" + + expected_result = [ + {column: "data" for column in VideoLiveChat.CHAT_MESSAGE_COLUMNS} + ] + + csv_file = StringIO() + csv_writer = csv.DictWriter(csv_file, fieldnames=expected_result[0].keys()) + csv_writer.writeheader() + csv_writer.writerows(expected_result) + + timestamp = datetime.now().strftime("%f") + output_file_name = f"output_{timestamp}.csv" + output_file_path = tmp_path / output_file_name + + videos_livechat_mock = Mock(return_value=expected_result) + youtube_mock.return_value.video_livechat = videos_livechat_mock + + result_file_path = VideoLiveChat.execute(id=video_id, output_file_path=output_file_path) + + with open(result_file_path, "r") as result_csv_file: + result_csv = result_csv_file.read() + + videos_livechat_mock.assert_called_once_with(video_id) + + assert result_csv.replace("\r", "") == csv_file.getvalue().replace("\r", "") From be977aa83a2ec810ae591dbc106f1b0244f6587f Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 16:08:54 -0300 Subject: [PATCH 81/85] add docstrings --- tests/commands/test_video_livechat.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/commands/test_video_livechat.py b/tests/commands/test_video_livechat.py index 6f22ad9..c91db87 100644 --- a/tests/commands/test_video_livechat.py +++ b/tests/commands/test_video_livechat.py @@ -8,6 +8,10 @@ def test_video_livechat(mocker): + """Test case for fetching live chat messages from a YouTube video. + + Mocks the YouTube API to return expected live chat messages and verifies if the execute method correctly formats and returns the data. + """ youtube_mock = mocker.patch("youtool.commands.video_livechat.YouTube") video_id = "video_id_mock" @@ -30,6 +34,10 @@ def test_video_livechat(mocker): def test_video_livechat_with_file_output(mocker, tmp_path): + """Test case for fetching live chat messages from a YouTube video and saving them to a CSV file. + + Mocks the YouTube API to return expected live chat messages and verifies if the execute method correctly saves the data to a CSV file. + """ youtube_mock = mocker.patch("youtool.commands.video_livechat.YouTube") video_id = "video_id_mock" From 93a94da7f6464bc3a68b3159a08592dc69d16475 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Wed, 3 Jul 2024 21:43:28 -0300 Subject: [PATCH 82/85] Add video_transcription command --- youtool/commands/video_transcription.py | 70 +++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 youtool/commands/video_transcription.py diff --git a/youtool/commands/video_transcription.py b/youtool/commands/video_transcription.py new file mode 100644 index 0000000..c0dace9 --- /dev/null +++ b/youtool/commands/video_transcription.py @@ -0,0 +1,70 @@ +import csv +from pathlib import Path +from typing import List, Dict +from .base import Command +from youtool import YouTube + +class VideoTranscription(Command): + """Download video transcriptions based on language code, path, and list of video IDs or URLs (or CSV filename with URLs/IDs inside). + Download files to destination and report results.""" + + name = "video-transcription" + arguments = [ + {"name": "--ids", "type": str, "help": "Video IDs", "nargs": "*"}, + {"name": "--urls", "type": str, "help": "Video URLs", "nargs": "*"}, + {"name": "--input-file-path", "type": str, "help": "CSV file path containing video IDs or URLs"}, + {"name": "--output-dir", "type": str, "help": "Output directory to save transcriptions"}, + {"name": "--language-code", "type": str, "help": "Language code for transcription"}, + {"name": "--api-key", "type": str, "help": "API key for YouTube Data API"}, + ] + + TRANSCRIPTION_COLUMNS: List[str] = [ + "video_id", "transcription_text" + ] + + @classmethod + def execute(cls, **kwargs) -> str: + """ + Execute the video-transcription command to download transcriptions of videos based on IDs or URLs and save them to files. + + Args: + ids: A list of YouTube video IDs. + urls: A list of YouTube video URLs. + input_file_path: Path to a CSV file containing YouTube video IDs or URLs. + output_dir: Directory path to save the transcription files. + language_code: Language code for the transcription language. + api_key: The API key to authenticate with the YouTube Data API. + + Returns: + A message indicating the result of the command. Reports success or failure for each video transcription download. + """ + ids = kwargs.get("ids") + urls = kwargs.get("urls") + input_file_path = kwargs.get("input_file_path") + output_dir = kwargs.get("output_dir") + language_code = kwargs.get("language_code") + api_key = kwargs.get("api_key") + + youtube = YouTube([api_key], disable_ipv6=True) + + if input_file_path: + ids += cls.data_from_csv(Path(input_file_path), "video_id") + + if urls: + ids += [cls.video_id_from_url(url) for url in urls] + + # Remove duplicated + ids = list(set(ids)) + + # youtube.videos_transcriptions(ids, language_code, output_dir) + + results = [] + for video_id in ids: + try: + transcription = youtube.video_transcription(video_id, language_code) + output_file_path = cls.save_transcription_to_file(video_id, transcription, output_dir) + results.append(f"Transcription saved to {output_file_path}") + except Exception as e: + results.append(f"Error processing video {video_id}: {str(e)}") + + return "\n".join(results) \ No newline at end of file From c9433ff4c33943362600bb4fb45e77b3d828ba25 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 01:00:03 -0300 Subject: [PATCH 83/85] - Add test for video_transcription command; - Add some necessary improvements in other files --- tests/commands/test_channel_info.py | 4 +- tests/commands/test_video_transcription.py | 56 ++++++++++++++++++++++ tests/test_cli.py | 13 +++-- youtool/commands/base.py | 11 ++++- youtool/commands/video_transcription.py | 47 ++++++++++-------- 5 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 tests/commands/test_video_transcription.py diff --git a/tests/commands/test_channel_info.py b/tests/commands/test_channel_info.py index 62623e8..3ae97c4 100644 --- a/tests/commands/test_channel_info.py +++ b/tests/commands/test_channel_info.py @@ -60,4 +60,6 @@ def test_channel_ids_from_urls_and_usernames(mocker, channels_urls): channel_id_from_username_mock.assert_has_calls( [call(username) for username in usernames] ) - channels_infos_mock.assert_called_once_with([ids_from_urls_mock, ids_from_usernames_mock]) + channels_infos_mock.assert_called_once() + assert ids_from_usernames_mock in channels_infos_mock.call_args.args[0] + assert ids_from_urls_mock in channels_infos_mock.call_args.args[0] diff --git a/tests/commands/test_video_transcription.py b/tests/commands/test_video_transcription.py new file mode 100644 index 0000000..6912c1b --- /dev/null +++ b/tests/commands/test_video_transcription.py @@ -0,0 +1,56 @@ +from unittest.mock import Mock + +from youtool.commands import VideoTranscription + + +def test_video_transcription(mocker, videos_ids, videos_urls, tmp_path): + youtube_mock = mocker.patch("youtool.commands.video_transcription.YouTube") + + language_code = "pt_br" + + videos_transcriptions_mock = Mock() + youtube_mock.return_value.videos_transcriptions = videos_transcriptions_mock + + for video_id in videos_ids: + open(tmp_path / f"{video_id}.{language_code}.vtt", "a").close() + + result = VideoTranscription.execute( + ids=videos_ids, urls=videos_urls, language_code=language_code, output_dir=tmp_path + ) + + videos_transcriptions_mock.assert_called_once_with( + list(set(videos_ids)), language_code, tmp_path + ) + + for video_id in videos_ids: + assert str(tmp_path / f"{video_id}.{language_code}.vtt") in result + + +def test_video_transcription_input_from_file(mocker, videos_ids, tmp_path): + youtube_mock = mocker.patch("youtool.commands.video_transcription.YouTube") + + language_code = "pt_br" + + videos_transcriptions_mock = Mock() + youtube_mock.return_value.videos_transcriptions = videos_transcriptions_mock + + input_file_path = tmp_path / "input_file.csv" + + with open(input_file_path, "w") as input_csv: + input_csv.write("video_id\n" + "\n".join(videos_ids)) + + for video_id in videos_ids: + open(tmp_path / f"{video_id}.{language_code}.vtt", "a").close() + + result = VideoTranscription.execute( + ids=None, urls=None, + language_code=language_code, output_dir=tmp_path, + input_file_path=input_file_path + ) + + videos_transcriptions_mock.assert_called_once_with( + list(set(videos_ids)), language_code, tmp_path + ) + + for video_id in videos_ids: + assert str(tmp_path / f"{video_id}.{language_code}.vtt") in result \ No newline at end of file diff --git a/tests/test_cli.py b/tests/test_cli.py index 9165041..92aa4fa 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,6 @@ import pytest +from pathlib import Path from subprocess import run from youtool.commands import COMMANDS @@ -17,9 +18,13 @@ def test_missing_api_key(monkeypatch: pytest.MonkeyPatch, command: Command): from the youtool CLI results in an appropriate error message and exit code. """ monkeypatch.delenv('YOUTUBE_API_KEY', raising=False) - cli_path = "youtool/cli.py" - command = ["python", cli_path, command.name] - result = run(command, capture_output=True, text=True, check=False) + cli_path = Path("youtool") / "cli.py" + command_string = ["python", cli_path, command.name] + for arg in command.arguments: + if arg.get("required"): + command_string.append(arg.get("name")) + command_string.append("test_value") + result = run(command_string, capture_output=True, text=True, check=False) assert result.returncode == 2 - assert "YouTube API Key is required" in result.stderr \ No newline at end of file + assert "YouTube API Key is required" in result.stderr diff --git a/youtool/commands/base.py b/youtool/commands/base.py index 20e1708..50068d6 100644 --- a/youtool/commands/base.py +++ b/youtool/commands/base.py @@ -80,7 +80,11 @@ def execute(cls, **kwargs) -> str: # noqa: D417 raise NotImplementedError() @staticmethod - def data_from_csv(file_path: Path, data_column_name: Optional[str] = None) -> List[str]: + def data_from_csv( + file_path: Path, + data_column_name: Optional[str] = None, + raise_column_exception: bool = True + ) -> List[str]: """Extracts a list of URLs from a specified CSV file. Args: @@ -107,7 +111,10 @@ def data_from_csv(file_path: Path, data_column_name: Optional[str] = None) -> Li raise ValueError("Fieldnames is None") if data_column_name not in fieldnames: - raise Exception(f"Column {data_column_name} not found on {file_path}") + if raise_column_exception: + raise Exception(f"Column {data_column_name} not found on {file_path}") + return data + for row in reader: value = row.get(data_column_name) if value is not None: diff --git a/youtool/commands/video_transcription.py b/youtool/commands/video_transcription.py index c0dace9..39a2f9f 100644 --- a/youtool/commands/video_transcription.py +++ b/youtool/commands/video_transcription.py @@ -16,11 +16,12 @@ class VideoTranscription(Command): {"name": "--output-dir", "type": str, "help": "Output directory to save transcriptions"}, {"name": "--language-code", "type": str, "help": "Language code for transcription"}, {"name": "--api-key", "type": str, "help": "API key for YouTube Data API"}, + {"name": "--url-column-name", "type": str, "help": "URL column name on csv input files"}, + {"name": "--id-column-name", "type": str, "help": "Channel ID column name on csv output files"} ] - TRANSCRIPTION_COLUMNS: List[str] = [ - "video_id", "transcription_text" - ] + ID_COLUMN_NAME: str = "video_id" + URL_COLUMN_NAME: str = "video_url" @classmethod def execute(cls, **kwargs) -> str: @@ -38,33 +39,39 @@ def execute(cls, **kwargs) -> str: Returns: A message indicating the result of the command. Reports success or failure for each video transcription download. """ - ids = kwargs.get("ids") - urls = kwargs.get("urls") + ids = kwargs.get("ids") or [] + urls = kwargs.get("urls") or [] input_file_path = kwargs.get("input_file_path") output_dir = kwargs.get("output_dir") language_code = kwargs.get("language_code") api_key = kwargs.get("api_key") + url_column_name = kwargs.get("url_column_name", cls.URL_COLUMN_NAME) + id_column_name = kwargs.get("id_column_name", cls.ID_COLUMN_NAME) + youtube = YouTube([api_key], disable_ipv6=True) - if input_file_path: - ids += cls.data_from_csv(Path(input_file_path), "video_id") + if (input_file_path := kwargs.get("input_file_path")): + if (urls_from_csv := cls.data_from_csv(input_file_path, url_column_name, False)): + ids += [cls.video_id_from_url(url) for url in urls_from_csv] + if (ids_from_csv := cls.data_from_csv(input_file_path, id_column_name, False)): + ids += ids_from_csv + + if not ids and not urls: + raise Exception( + "Either 'ids' or 'urls' must be provided for the video-transcription command" + ) if urls: ids += [cls.video_id_from_url(url) for url in urls] # Remove duplicated ids = list(set(ids)) - - # youtube.videos_transcriptions(ids, language_code, output_dir) - - results = [] - for video_id in ids: - try: - transcription = youtube.video_transcription(video_id, language_code) - output_file_path = cls.save_transcription_to_file(video_id, transcription, output_dir) - results.append(f"Transcription saved to {output_file_path}") - except Exception as e: - results.append(f"Error processing video {video_id}: {str(e)}") - - return "\n".join(results) \ No newline at end of file + youtube.videos_transcriptions(ids, language_code, output_dir) + output_dir_path = Path(output_dir) + saved_transcriptions = [ + str( + output_dir_path / f"{v_id}.{language_code}.vtt" + ) for v_id in ids if (output_dir_path / f"{v_id}.{language_code}.vtt").is_file() + ] + return "\n".join(saved_transcriptions) From b1d2fdfecdef5547d57eee76eb2336048ab1daf8 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 01:22:00 -0300 Subject: [PATCH 84/85] remove comments --- youtool/cli.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/youtool/cli.py b/youtool/cli.py index 28b055c..49dfe12 100644 --- a/youtool/cli.py +++ b/youtool/cli.py @@ -25,9 +25,6 @@ def main(): subparsers = parser.add_subparsers(required=True, dest="command", title="Command", help="Command to be executed") - # cmd_video_livechat = subparsers.add_parser("video-livechat", help="Get comments from a video ID, generate CSV output (same schema for `chat_message` dicts)") - # cmd_video_transcriptions = subparsers.add_parser("video-transcription", help="Download video transcriptions based on language code, path and list of video IDs or URLs (or CSV filename with URLs/IDs inside), download files to destination and report results") - for command in COMMANDS: command.parse_arguments(subparsers) From 160cecd08f99380a282fafb14f90d72332a9ebf0 Mon Sep 17 00:00:00 2001 From: aninhasalesp Date: Fri, 5 Jul 2024 16:16:52 -0300 Subject: [PATCH 85/85] add docstrings --- tests/commands/test_video_transcription.py | 14 ++++++++++++++ youtool/commands/video_transcription.py | 19 ++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/tests/commands/test_video_transcription.py b/tests/commands/test_video_transcription.py index 6912c1b..d3ee1f3 100644 --- a/tests/commands/test_video_transcription.py +++ b/tests/commands/test_video_transcription.py @@ -4,6 +4,13 @@ def test_video_transcription(mocker, videos_ids, videos_urls, tmp_path): + """ + Test the video transcription command. + + This test verifies the functionality of the VideoTranscription.execute method. + It mocks the YouTube API to simulate fetching transcriptions for given video IDs or URLs. + Transcriptions are expected to be saved in VTT format in the specified temporary directory. + """ youtube_mock = mocker.patch("youtool.commands.video_transcription.YouTube") language_code = "pt_br" @@ -27,6 +34,13 @@ def test_video_transcription(mocker, videos_ids, videos_urls, tmp_path): def test_video_transcription_input_from_file(mocker, videos_ids, tmp_path): + """Test the video transcription command with input from a CSV file. + + This test verifies the functionality of the VideoTranscription.execute method when + video IDs are provided via a CSV file. It mocks the YouTube API to simulate fetching + transcriptions for the listed video IDs. Transcriptions are expected to be saved in + VTT format in the specified temporary directory. + """ youtube_mock = mocker.patch("youtool.commands.video_transcription.YouTube") language_code = "pt_br" diff --git a/youtool/commands/video_transcription.py b/youtool/commands/video_transcription.py index 39a2f9f..e895e5a 100644 --- a/youtool/commands/video_transcription.py +++ b/youtool/commands/video_transcription.py @@ -25,19 +25,20 @@ class VideoTranscription(Command): @classmethod def execute(cls, **kwargs) -> str: - """ - Execute the video-transcription command to download transcriptions of videos based on IDs or URLs and save them to files. + """Execute the video-transcription command to download transcriptions of videos based on IDs or URLs and save them to files. Args: - ids: A list of YouTube video IDs. - urls: A list of YouTube video URLs. - input_file_path: Path to a CSV file containing YouTube video IDs or URLs. - output_dir: Directory path to save the transcription files. - language_code: Language code for the transcription language. - api_key: The API key to authenticate with the YouTube Data API. + ids (List[str]): A list of YouTube video IDs. + urls (List[str]): A list of YouTube video URLs. + input_file_path (str): Path to a CSV file containing YouTube video IDs or URLs. + output_dir (str): Directory path to save the transcription files. + language_code (str): Language code for the transcription language. + api_key (str): The API key to authenticate with the YouTube Data API. + url_column_name (str, optional): Column name for URLs in the CSV input file. Defaults to "video_url". + id_column_name (str, optional): Column name for IDs in the CSV output file. Defaults to "video_id". Returns: - A message indicating the result of the command. Reports success or failure for each video transcription download. + str: A message indicating the result of the command. Reports success or failure for each video transcription download. """ ids = kwargs.get("ids") or [] urls = kwargs.get("urls") or []