package me.harshithgoka.youtubedl.Activities; import android.Manifest; import android.app.DownloadManager; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.appcompat.app.AppCompatActivity; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.text.Html; import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.util.LongSparseArray; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.EditorInfo; import android.webkit.ValueCallback; import android.webkit.WebSettings; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.button.MaterialButton; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.io.File; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import androidx.core.util.Pair; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import me.harshithgoka.youtubedl.YoutubeDL.Extractor; import me.harshithgoka.youtubedl.YoutubeDL.Format; import me.harshithgoka.youtubedl.Adapters.FormatAdapter; import me.harshithgoka.youtubedl.R; import me.harshithgoka.youtubedl.CustomUI.RecyclerViewEmptySupport; import me.harshithgoka.youtubedl.YoutubeDL.Utils.FormatUtils; import me.harshithgoka.youtubedl.YoutubeDL.VideoInfo; import me.harshithgoka.youtubedl.Adapters.VideoInfoAdapter; public class MainActivity extends AppCompatActivity implements View.OnClickListener { static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 9293; static final String HISTORY = "VideoInfos"; EditText urlEdit; TextView log; MaterialButton btnCopy; List<Format> formats; Extractor extractor; Pattern youtubeUrlPattern; RecyclerViewEmptySupport formatsRecyclerView; FormatAdapter formatAdapter; LinearLayoutManager formatLinearLayoutManager; RecyclerViewEmptySupport viRecyclerView; VideoInfoAdapter viAdapter; LinearLayoutManager viLinearLayoutManager; BottomSheetBehavior<View> bottomSheetBehavior; List<ProgressBar> progressBars; SharedPreferences mPrefs; SharedPreferences sharedPreferences; ArrayList<VideoInfo> history; TextView videoTitle; Gson gson; HashMap<Long, Format> inProgressDownloads; ArrayList<Pair<Format, Format>> mixingDownloads; @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); } return; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); } String appLogoText = "<font color=#c62828>Y</font>ou<font color=#e15827>T</font>ube<font color=#33745f>DL</font>"; TextView appLogo = findViewById(R.id.app_logo); appLogo.setText(Html.fromHtml(appLogoText)); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayShowTitleEnabled(false); } this.btnCopy = findViewById(R.id.paste); this.btnCopy.setOnClickListener(this); log = (TextView) findViewById(R.id.textView); log.setMovementMethod(new ScrollingMovementMethod()); urlEdit = (EditText) findViewById(R.id.url); urlEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { switch (actionId) { case EditorInfo.IME_ACTION_GO: Log.d("URL", v.getText().toString()); startPoint(null); return true; default: Log.d("Edit text GOT", actionId + " " + event.getKeyCode()); return false; } } }); progressBars = new ArrayList<>(); progressBars.add((ProgressBar) findViewById(R.id.progressBar)); progressBars.add((ProgressBar) findViewById(R.id.progressBar2)); formats = new ArrayList<>(); extractor = new Extractor(); youtubeUrlPattern = Pattern.compile(extractor._VALID_URL); // Formats Holder Bottom Sheet bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.bottom_sheet)); bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); videoTitle = findViewById(R.id.video_title); // Formats formatsRecyclerView = findViewById(R.id.recycler_view); formatAdapter = new FormatAdapter(getApplicationContext(), formats, this); formatsRecyclerView.setAdapter(formatAdapter); formatLinearLayoutManager = new LinearLayoutManager(getApplicationContext()); formatsRecyclerView.setLayoutManager(formatLinearLayoutManager); // History Get Content mPrefs = getPreferences(MODE_PRIVATE); gson = new Gson(); String json = mPrefs.getString(HISTORY, ""); Type type = new TypeToken< List < VideoInfo >>() {}.getType(); history = gson.fromJson(json, type); if (history == null) { history = new ArrayList<>(); } sharedPreferences = getSharedPreferences("download_history", Context.MODE_PRIVATE); json = sharedPreferences.getString("in_progress", ""); type = new TypeToken<HashMap<Long, Format>>() {}.getType(); inProgressDownloads = gson.fromJson(json, type); if (inProgressDownloads == null) { inProgressDownloads = new HashMap<Long, Format>(); } json = sharedPreferences.getString("mixing_downloads", ""); type = new TypeToken<ArrayList<Pair<Format, Format>>>() {}.getType(); mixingDownloads = gson.fromJson(json, type); if (mixingDownloads == null) { mixingDownloads = new ArrayList<>(); } // History viRecyclerView = findViewById(R.id.historyRecyclerView); viAdapter = new VideoInfoAdapter(this, history); viRecyclerView.setEmptyView(findViewById(R.id.empty_history)); viRecyclerView.setAdapter(viAdapter); viLinearLayoutManager = new LinearLayoutManager(getApplicationContext()); viRecyclerView.setLayoutManager(viLinearLayoutManager); // Best Download MaterialButton btn = findViewById(R.id.best_download); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Format f1 = null, f2 = null; for (Format format: formats) { if (format.getItag() == 137) { f1 = format; } if (format.getItag() == 140) { f2 = format; } } Pair<Format, Format> p = new Pair<>(f1, f2); if (f1 == null || f2 == null) { return; } download(f1); download(f2); mixingDownloads.add(p); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("mixing_downloads", gson.toJson(mixingDownloads)); editor.apply(); } }); // ATTENTION: This was auto-generated to handle app links. Intent appLinkIntent = getIntent(); String appLinkAction = appLinkIntent.getAction(); ClipData clipData = appLinkIntent.getClipData(); Uri appLinkData = appLinkIntent.getData(); String url = null; if (appLinkData != null) { url = appLinkData.toString(); } else if (clipData != null) { if (clipData.getItemCount() > 0) url = clipData.getItemAt(0).getText().toString(); } if (url != null) { urlEdit.setText(url); startDownload(url); } } @Override protected void onPause() { super.onPause(); SharedPreferences.Editor editor = mPrefs.edit(); String connectionsJSONString = gson.toJson(history); editor.putString(HISTORY, connectionsJSONString); editor.apply(); } @Override public void onBackPressed() { if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) { super.onBackPressed(); } else { bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. Log.d("INFO", "onCreateOptionsMenu called"); getMenuInflater().inflate(R.menu.menu_formats, menu); return true; } public void openSettingPage() { Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case R.id.action_settings: Toast.makeText(getApplicationContext(), "Coming soon", Toast.LENGTH_LONG).show(); // openSettingPage(); return true; default: break; } return false; } private void println (String s) { log.append(s + "\n"); } public String preprocess (String s) { int index = s.lastIndexOf("#"); if (index > 0) { s = s.substring(0, index); } s = s.replaceFirst("m.youtube.com", "www.youtube.com"); s = s.replaceFirst("&.*", ""); return s; } public void startDownload(String url) { url = preprocess(url); java.util.regex.Matcher m = youtubeUrlPattern.matcher(url); println("Url: " + url); urlEdit.setText(url); if (!m.find()) { Toast.makeText(this, "Invalid Youtube URL", Toast.LENGTH_SHORT).show(); return; } AsyncTask<String, Void, VideoInfo> asyncTask = new YoutubeDLAsyncTask(getApplicationContext(), extractor); asyncTask.execute(url); } public void startPoint(View button) { String url = urlEdit.getText().toString(); startDownload(url); } public void pasteFromClipboard(View button) { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); if (clipboard != null && clipboard.hasPrimaryClip()) { ClipData clipData = clipboard.getPrimaryClip(); Uri uri; CharSequence url; if ( clipData != null && clipData.getItemCount() > 0 ) { if ((uri = clipData.getItemAt(0).getUri()) != null) { urlEdit.setText(uri.toString()); } else if ((url = clipData.getItemAt(0).getText()) != null ) { urlEdit.setText(url.toString()); } startPoint(button); } } } public void showLoading () { for (ProgressBar bar : progressBars) { bar.setVisibility(View.VISIBLE); } } public void hideLoading() { for (ProgressBar bar: progressBars) { bar.setVisibility(View.GONE); } } public void loadVideoInfo(VideoInfo videoInfo) { Log.d("II", "Loading videoInfo"); int numRemoved = formats.size(); formats.clear(); formatAdapter.notifyItemRangeRemoved(0, numRemoved); formats.addAll(videoInfo.getFormats()); formatAdapter.notifyItemRangeInserted(0, videoInfo.getFormats().size()); videoTitle.setText(videoInfo.getTitle()); bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.paste: pasteFromClipboard(v); break; } } class YoutubeDLAsyncTask extends AsyncTask<String, Void, VideoInfo> { Context context; Extractor ytextractor; public YoutubeDLAsyncTask(Context context, Extractor extractor) { this.context = context; ytextractor = extractor; } @Override protected void onPreExecute() { super.onPreExecute(); showLoading(); } @Override protected VideoInfo doInBackground(String... strings) { String you_url = strings[0]; return ytextractor.getFormats(you_url); } @Override protected void onPostExecute(VideoInfo videoInfo) { hideLoading(); if (videoInfo != null) { List<Format> formats = videoInfo.getFormats(); if (formats.size() > 0) { int index; if ((index = history.indexOf(videoInfo)) != -1) { history.remove(index); history.add(0, videoInfo); viAdapter.notifyItemMoved(index, 0); } else { history.add(0, videoInfo); viAdapter.notifyItemInserted(0); } loadVideoInfo(videoInfo); String finalurl = formats.get(0).getUrl(); println(finalurl); ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); assert clipboard != null; ClipData clip = ClipData.newRawUri("DownloadURL", Uri.parse(finalurl)); clipboard.setPrimaryClip(clip); Toast.makeText(getApplicationContext(), String.format("Best quality link (%s) copied to Clipboard", formats.get(0).getQuality()), Toast.LENGTH_SHORT).show(); } else { println("No. of formats: 0"); Toast.makeText(getApplicationContext(), "Not yet implemented encrypted signature", Toast.LENGTH_SHORT).show(); } } else { println("Error connecting to the Internet"); } } }; public String getDownloadDirectory(Context context) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); return sharedPreferences.getString("download_folder", Environment.DIRECTORY_DOWNLOADS); } public String greatestCommonPrefix(String a, String b) { int minLength = Math.min(a.length(), b.length()); for (int i = 0; i < minLength; i++) { if (a.charAt(i) != b.charAt(i)) { return a.substring(0, i); } } return a.substring(0, minLength); } public void download (Format format) { String extension = FormatUtils.INSTANCE.getExtension(format); String filename = format.sanitizeFilename() + "." + extension; Log.d("Filename", filename); String final_download_directory = getDownloadDirectory(getApplicationContext()); DownloadManager dm =null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { dm = getSystemService(DownloadManager.class); }else{ dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); } // Temporary Download folder File[] files = ContextCompat.getExternalFilesDirs(getApplicationContext(), Environment.DIRECTORY_MOVIES); int maximum_length = 0; int which_index = -1; String s; for (int i = 0; i < files.length; i++) { s = greatestCommonPrefix(final_download_directory, files[i].getAbsolutePath()); if (s.length() > maximum_length) { maximum_length = s.length(); which_index = i; } } Uri uri = null; if (which_index > -1) { uri = Uri.fromFile(files[which_index]); } DownloadManager.Request req = new DownloadManager.Request(Uri.parse(format.getUrl())); req.setTitle(filename); req.setDescription(final_download_directory + File.separator + filename); if (uri != null) { req.setDestinationUri(uri); } else { req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, File.separator + filename); } req.allowScanningByMediaScanner(); req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); long download_id = dm.enqueue(req); inProgressDownloads.put(download_id, format); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("in_progress", gson.toJson(inProgressDownloads)); editor.apply(); Toast.makeText(getApplicationContext(), String.format("Your media file now downloading to \"%s\" folder. Check the notification area.", final_download_directory), Toast.LENGTH_SHORT).show(); format.setDowmloadState(Format.DownloadState.DOWNLOADING); } }