{
13 |
14 | String name;
15 | String url;
16 | String vcodec;
17 | String acodec;
18 | String width;
19 | String height;
20 | String ext;
21 | String fileSize;
22 | String formatId;
23 |
24 | public Format(final JsonObject item) {
25 | name = JsonHelper.getAsString(item, "format");
26 | url = JsonHelper.getAsString(item, "url");
27 | acodec = JsonHelper.getAsString(item, "acodec");
28 | vcodec = JsonHelper.getAsString(item, "vcodec");
29 | width = JsonHelper.getAsString(item, "width");
30 | height = JsonHelper.getAsString(item, "height");
31 | ext = JsonHelper.getAsString(item, "ext");
32 | fileSize = JsonHelper.getAsString(item, "filesize");
33 | formatId = JsonHelper.getAsString(item, "format_id");
34 | }
35 |
36 | public String getName() {
37 | return name;
38 | }
39 |
40 | public String getUrl() {
41 | return url;
42 | }
43 |
44 | public String getVcodec() {
45 | return vcodec;
46 | }
47 |
48 | public String getAcodec() {
49 | return acodec;
50 | }
51 |
52 | public String getWidth() {
53 | return width;
54 | }
55 |
56 | public String getHeight() {
57 | return height;
58 | }
59 |
60 | public String getExt() {
61 | return ext;
62 | }
63 |
64 | public String getFileSize() {
65 | return fileSize;
66 | }
67 |
68 | public String getFormatId() {
69 | return formatId;
70 | }
71 |
72 | @Override
73 | public int compareTo(@NonNull final Format o) {
74 | if(formatId != null) {
75 | return formatId.compareTo(o.getFormatId());
76 | }
77 | return 1;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/org/redwid/android/youtube/dl/app/utils/JsonHelper.java:
--------------------------------------------------------------------------------
1 | package org.redwid.android.youtube.dl.app.utils;
2 |
3 | import com.google.gson.JsonObject;
4 |
5 | /**
6 | * The JsonHelper class.
7 | */
8 | public final class JsonHelper {
9 |
10 | public static String getAsString(final JsonObject item, final String key) {
11 | return getAsString(item, key, "");
12 | }
13 |
14 | public static String getAsString(final JsonObject item, final String key, final String defaultValue) {
15 | if(item != null && item.has(key)) {
16 | try {
17 | return item.get(key).getAsString();
18 | } catch(UnsupportedOperationException ignore) {
19 |
20 | }
21 | }
22 | return defaultValue;
23 | }
24 |
25 | public static long getAsLong(final JsonObject item, final String key) {
26 | if(item != null && item.has(key)) {
27 | try {
28 | return item.get(key).getAsLong();
29 | } catch(UnsupportedOperationException ignore) {
30 |
31 | }
32 | }
33 | return 0;
34 | }
35 |
36 | public static int getAsInt(final JsonObject item, final String key) {
37 | if(item != null && item.has(key)) {
38 | try {
39 | return item.get(key).getAsInt();
40 | } catch(UnsupportedOperationException ignore) {
41 |
42 | }
43 | }
44 | return 0;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
16 |
21 |
26 |
31 |
36 |
41 |
46 |
51 |
56 |
61 |
66 |
71 |
76 |
81 |
86 |
91 |
96 |
101 |
106 |
111 |
116 |
121 |
126 |
131 |
136 |
141 |
146 |
151 |
156 |
161 |
166 |
171 |
172 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/list_longpressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/drawable/list_longpressed.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/list_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/drawable/list_pressed.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/list_selector_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/list_selector_background_disabled.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/drawable/list_selector_background_disabled.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/list_selector_background_focus.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/drawable/list_selector_background_focus.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/list_selector_background_transition.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rounded_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
25 |
26 |
42 |
43 |
54 |
55 |
66 |
67 |
79 |
80 |
93 |
94 |
103 |
104 |
105 |
117 |
118 |
129 |
130 |
140 |
141 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/pop_up_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
25 |
26 |
43 |
44 |
62 |
63 |
70 |
71 |
72 |
87 |
88 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
22 |
23 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #00000000
7 | #80CCCCCC
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Android Youtube-Dl
3 | Open Youtube application and select share video with "Android Youtube-Dl" application
4 | %s\n\nFinished: %d\nRunning:\n%s
5 | Loading ...
6 | OK
7 | ERROR
8 | Unable to finish request for the url: %s
9 | %d. %s
10 | Cancel All Works
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.3'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | maven { url "https://dl.bintray.com/redwid/maven" }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | org.gradle.jvmargs=-Xmx1536m
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. More details, visit
14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
15 | # org.gradle.parallel=true
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Redwid/android-youtube-dl/960e7ea545b73c26afda6c91bd19b7d853f7216b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/lib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'maven'
3 |
4 | group = 'org.redwid.android'
5 | description = 'youtube-dl'
6 | version = '0.0.3'
7 |
8 |
9 | android {
10 | compileSdkVersion 28
11 |
12 | defaultConfig {
13 | minSdkVersion 14
14 | targetSdkVersion 28
15 | versionCode 1
16 | versionName "1.0.3"
17 |
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 |
20 | }
21 |
22 | task sourcesJar(type: Jar) {
23 | from android.sourceSets.main.java.srcDirs
24 | classifier = 'sources'
25 | }
26 | }
27 |
28 | dependencies {
29 | implementation 'androidx.appcompat:appcompat:1.1.0'
30 | implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
31 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
32 |
33 | implementation 'com.jakewharton.timber:timber:4.7.1'
34 |
35 | implementation 'org.redwid.android.youtube.dl:python:0.6.8:arm64-v8a@aar'
36 | implementation 'org.redwid.android.youtube.dl:python:0.6.8:armeabi-v7a@aar'
37 |
38 | testImplementation 'junit:junit:4.12'
39 | androidTestImplementation 'androidx.test:runner:1.3.0-alpha05'
40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0-alpha05'
41 | }
42 |
43 | uploadArchives {
44 | repositories {
45 | mavenDeployer {
46 | pom.artifactId = project.description
47 | repository(url: "file://${System.env.HOME}/.m2/repository/")
48 | }
49 | }
50 | }
51 |
52 | artifacts {
53 | archives sourcesJar
54 | }
--------------------------------------------------------------------------------
/lib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/kamranzafar/jtar/Octal.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 Kamran Zafar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package org.kamranzafar.jtar;
19 |
20 | /**
21 | * @author Kamran Zafar
22 | *
23 | */
24 | public class Octal {
25 |
26 | /**
27 | * Parse an octal string from a header buffer. This is used for the file
28 | * permission mode value.
29 | *
30 | * @param header
31 | * The header buffer from which to parse.
32 | * @param offset
33 | * The offset into the buffer from which to parse.
34 | * @param length
35 | * The number of header bytes to parse.
36 | *
37 | * @return The long value of the octal string.
38 | */
39 | public static long parseOctal(byte[] header, int offset, int length) {
40 | long result = 0;
41 | boolean stillPadding = true;
42 |
43 | int end = offset + length;
44 | for (int i = offset; i < end; ++i) {
45 | if (header[i] == 0)
46 | break;
47 |
48 | if (header[i] == (byte) ' ' || header[i] == '0') {
49 | if (stillPadding)
50 | continue;
51 |
52 | if (header[i] == (byte) ' ')
53 | break;
54 | }
55 |
56 | stillPadding = false;
57 |
58 | result = ( result << 3 ) + ( header[i] - '0' );
59 | }
60 |
61 | return result;
62 | }
63 |
64 | /**
65 | * Parse an octal integer from a header buffer.
66 | *
67 | * @param value
68 | * @param buf
69 | * The header buffer from which to parse.
70 | * @param offset
71 | * The offset into the buffer from which to parse.
72 | * @param length
73 | * The number of header bytes to parse.
74 | *
75 | * @return The integer value of the octal bytes.
76 | */
77 | public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
78 | int idx = length - 1;
79 |
80 | buf[offset + idx] = 0;
81 | --idx;
82 | buf[offset + idx] = (byte) ' ';
83 | --idx;
84 |
85 | if (value == 0) {
86 | buf[offset + idx] = (byte) '0';
87 | --idx;
88 | } else {
89 | for (long val = value; idx >= 0 && val > 0; --idx) {
90 | buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
91 | val = val >> 3;
92 | }
93 | }
94 |
95 | for (; idx >= 0; --idx) {
96 | buf[offset + idx] = (byte) ' ';
97 | }
98 |
99 | return offset + length;
100 | }
101 |
102 | /**
103 | * Parse the checksum octal integer from a header buffer.
104 | *
105 | * @param value
106 | * @param buf
107 | * The header buffer from which to parse.
108 | * @param offset
109 | * The offset into the buffer from which to parse.
110 | * @param length
111 | * The number of header bytes to parse.
112 | * @return The integer value of the entry's checksum.
113 | */
114 | public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
115 | getOctalBytes( value, buf, offset, length );
116 | buf[offset + length - 1] = (byte) ' ';
117 | buf[offset + length - 2] = 0;
118 | return offset + length;
119 | }
120 |
121 | /**
122 | * Parse an octal long integer from a header buffer.
123 | *
124 | * @param value
125 | * @param buf
126 | * The header buffer from which to parse.
127 | * @param offset
128 | * The offset into the buffer from which to parse.
129 | * @param length
130 | * The number of header bytes to parse.
131 | *
132 | * @return The long value of the octal bytes.
133 | */
134 | public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
135 | byte[] temp = new byte[length + 1];
136 | getOctalBytes( value, temp, 0, length + 1 );
137 | System.arraycopy( temp, 0, buf, offset, length );
138 | return offset + length;
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/kamranzafar/jtar/TarConstants.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 Kamran Zafar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package org.kamranzafar.jtar;
19 |
20 | /**
21 | * @author Kamran Zafar
22 | *
23 | */
24 | public class TarConstants {
25 | public static final int EOF_BLOCK = 1024;
26 | public static final int DATA_BLOCK = 512;
27 | public static final int HEADER_BLOCK = 512;
28 | }
29 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/kamranzafar/jtar/TarEntry.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 Kamran Zafar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package org.kamranzafar.jtar;
19 |
20 | import java.io.File;
21 | import java.util.Date;
22 |
23 | /**
24 | * @author Kamran Zafar
25 | *
26 | */
27 | public class TarEntry {
28 | protected File file;
29 | protected TarHeader header;
30 |
31 | private TarEntry() {
32 | this.file = null;
33 | header = new TarHeader();
34 | }
35 |
36 | public TarEntry(File file, String entryName) {
37 | this();
38 | this.file = file;
39 | this.extractTarHeader(entryName);
40 | }
41 |
42 | public TarEntry(byte[] headerBuf) {
43 | this();
44 | this.parseTarHeader(headerBuf);
45 | }
46 |
47 | /**
48 | * Constructor to create an entry from an existing TarHeader object.
49 | *
50 | * This method is useful to add new entries programmatically (e.g. for
51 | * adding files or directories that do not exist in the file system).
52 | *
53 | * @param header
54 | *
55 | */
56 | public TarEntry(TarHeader header) {
57 | this.file = null;
58 | this.header = header;
59 | }
60 |
61 | public boolean equals(TarEntry it) {
62 | return header.name.toString().equals(it.header.name.toString());
63 | }
64 |
65 | public boolean isDescendent(TarEntry desc) {
66 | return desc.header.name.toString().startsWith(header.name.toString());
67 | }
68 |
69 | public TarHeader getHeader() {
70 | return header;
71 | }
72 |
73 | public String getName() {
74 | String name = header.name.toString();
75 | if (header.namePrefix != null && !header.namePrefix.toString().equals("")) {
76 | name = header.namePrefix.toString() + "/" + name;
77 | }
78 |
79 | return name;
80 | }
81 |
82 | public void setName(String name) {
83 | header.name = new StringBuffer(name);
84 | }
85 |
86 | public int getUserId() {
87 | return header.userId;
88 | }
89 |
90 | public void setUserId(int userId) {
91 | header.userId = userId;
92 | }
93 |
94 | public int getGroupId() {
95 | return header.groupId;
96 | }
97 |
98 | public void setGroupId(int groupId) {
99 | header.groupId = groupId;
100 | }
101 |
102 | public String getUserName() {
103 | return header.userName.toString();
104 | }
105 |
106 | public void setUserName(String userName) {
107 | header.userName = new StringBuffer(userName);
108 | }
109 |
110 | public String getGroupName() {
111 | return header.groupName.toString();
112 | }
113 |
114 | public void setGroupName(String groupName) {
115 | header.groupName = new StringBuffer(groupName);
116 | }
117 |
118 | public void setIds(int userId, int groupId) {
119 | this.setUserId(userId);
120 | this.setGroupId(groupId);
121 | }
122 |
123 | public void setModTime(long time) {
124 | header.modTime = time / 1000;
125 | }
126 |
127 | public void setModTime(Date time) {
128 | header.modTime = time.getTime() / 1000;
129 | }
130 |
131 | public Date getModTime() {
132 | return new Date(header.modTime * 1000);
133 | }
134 |
135 | public File getFile() {
136 | return this.file;
137 | }
138 |
139 | public long getSize() {
140 | return header.size;
141 | }
142 |
143 | public void setSize(long size) {
144 | header.size = size;
145 | }
146 |
147 | /**
148 | * Checks if the org.kamrazafar.jtar entry is a directory
149 | *
150 | * @return
151 | */
152 | public boolean isDirectory() {
153 | if (this.file != null)
154 | return this.file.isDirectory();
155 |
156 | if (header != null) {
157 | if (header.linkFlag == TarHeader.LF_DIR)
158 | return true;
159 |
160 | if (header.name.toString().endsWith("/"))
161 | return true;
162 | }
163 |
164 | return false;
165 | }
166 |
167 | /**
168 | * Extract header from File
169 | *
170 | * @param entryName
171 | */
172 | public void extractTarHeader(String entryName) {
173 | header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory());
174 | }
175 |
176 | /**
177 | * Calculate checksum
178 | *
179 | * @param buf
180 | * @return
181 | */
182 | public long computeCheckSum(byte[] buf) {
183 | long sum = 0;
184 |
185 | for (int i = 0; i < buf.length; ++i) {
186 | sum += 255 & buf[i];
187 | }
188 |
189 | return sum;
190 | }
191 |
192 | /**
193 | * Writes the header to the byte buffer
194 | *
195 | * @param outbuf
196 | */
197 | public void writeEntryHeader(byte[] outbuf) {
198 | int offset = 0;
199 |
200 | offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN);
201 | offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN);
202 | offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN);
203 | offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN);
204 |
205 | long size = header.size;
206 |
207 | offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
208 | offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN);
209 |
210 | int csOffset = offset;
211 | for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
212 | outbuf[offset++] = (byte) ' ';
213 |
214 | outbuf[offset++] = header.linkFlag;
215 |
216 | offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN);
217 | offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN);
218 | offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN);
219 | offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN);
220 | offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN);
221 | offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN);
222 | offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX);
223 |
224 | for (; offset < outbuf.length;)
225 | outbuf[offset++] = 0;
226 |
227 | long checkSum = this.computeCheckSum(outbuf);
228 |
229 | Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN);
230 | }
231 |
232 | /**
233 | * Parses the tar header to the byte buffer
234 | *
235 | * @param header
236 | * @param bh
237 | */
238 | public void parseTarHeader(byte[] bh) {
239 | int offset = 0;
240 |
241 | header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
242 | offset += TarHeader.NAMELEN;
243 |
244 | header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
245 | offset += TarHeader.MODELEN;
246 |
247 | header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
248 | offset += TarHeader.UIDLEN;
249 |
250 | header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
251 | offset += TarHeader.GIDLEN;
252 |
253 | header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
254 | offset += TarHeader.SIZELEN;
255 |
256 | header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
257 | offset += TarHeader.MODTIMELEN;
258 |
259 | header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN);
260 | offset += TarHeader.CHKSUMLEN;
261 |
262 | header.linkFlag = bh[offset++];
263 |
264 | header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
265 | offset += TarHeader.NAMELEN;
266 |
267 | header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
268 | offset += TarHeader.USTAR_MAGICLEN;
269 |
270 | header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN);
271 | offset += TarHeader.USTAR_USER_NAMELEN;
272 |
273 | header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN);
274 | offset += TarHeader.USTAR_GROUP_NAMELEN;
275 |
276 | header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
277 | offset += TarHeader.USTAR_DEVLEN;
278 |
279 | header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
280 | offset += TarHeader.USTAR_DEVLEN;
281 |
282 | header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX);
283 | }
284 | }
--------------------------------------------------------------------------------
/lib/src/main/java/org/kamranzafar/jtar/TarHeader.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 Kamran Zafar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package org.kamranzafar.jtar;
19 |
20 | import java.io.File;
21 |
22 | /**
23 | * Header
24 | *
25 | *
26 | * Offset Size Field
27 | * 0 100 File name
28 | * 100 8 File mode
29 | * 108 8 Owner's numeric user ID
30 | * 116 8 Group's numeric user ID
31 | * 124 12 File size in bytes
32 | * 136 12 Last modification time in numeric Unix time format
33 | * 148 8 Checksum for header block
34 | * 156 1 Link indicator (file type)
35 | * 157 100 Name of linked file
36 | *
37 | *
38 | *
39 | * File Types
40 | *
41 | *
42 | * Value Meaning
43 | * '0' Normal file
44 | * (ASCII NUL) Normal file (now obsolete)
45 | * '1' Hard link
46 | * '2' Symbolic link
47 | * '3' Character special
48 | * '4' Block special
49 | * '5' Directory
50 | * '6' FIFO
51 | * '7' Contigous
52 | *
53 | *
54 | *
55 | *
56 | * Ustar header
57 | *
58 | *
59 | * Offset Size Field
60 | * 257 6 UStar indicator "ustar"
61 | * 263 2 UStar version "00"
62 | * 265 32 Owner user name
63 | * 297 32 Owner group name
64 | * 329 8 Device major number
65 | * 337 8 Device minor number
66 | * 345 155 Filename prefix
67 | *
68 | */
69 |
70 | public class TarHeader {
71 |
72 | /*
73 | * Header
74 | */
75 | public static final int NAMELEN = 100;
76 | public static final int MODELEN = 8;
77 | public static final int UIDLEN = 8;
78 | public static final int GIDLEN = 8;
79 | public static final int SIZELEN = 12;
80 | public static final int MODTIMELEN = 12;
81 | public static final int CHKSUMLEN = 8;
82 | public static final byte LF_OLDNORM = 0;
83 |
84 | /*
85 | * File Types
86 | */
87 | public static final byte LF_NORMAL = (byte) '0';
88 | public static final byte LF_LINK = (byte) '1';
89 | public static final byte LF_SYMLINK = (byte) '2';
90 | public static final byte LF_CHR = (byte) '3';
91 | public static final byte LF_BLK = (byte) '4';
92 | public static final byte LF_DIR = (byte) '5';
93 | public static final byte LF_FIFO = (byte) '6';
94 | public static final byte LF_CONTIG = (byte) '7';
95 |
96 | /*
97 | * Ustar header
98 | */
99 |
100 | public static final String USTAR_MAGIC = "ustar"; // POSIX
101 |
102 | public static final int USTAR_MAGICLEN = 8;
103 | public static final int USTAR_USER_NAMELEN = 32;
104 | public static final int USTAR_GROUP_NAMELEN = 32;
105 | public static final int USTAR_DEVLEN = 8;
106 | public static final int USTAR_FILENAME_PREFIX = 155;
107 |
108 | // Header values
109 | public StringBuffer name;
110 | public int mode;
111 | public int userId;
112 | public int groupId;
113 | public long size;
114 | public long modTime;
115 | public int checkSum;
116 | public byte linkFlag;
117 | public StringBuffer linkName;
118 | public StringBuffer magic; // ustar indicator and version
119 | public StringBuffer userName;
120 | public StringBuffer groupName;
121 | public int devMajor;
122 | public int devMinor;
123 | public StringBuffer namePrefix;
124 |
125 | public TarHeader() {
126 | this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
127 |
128 | this.name = new StringBuffer();
129 | this.linkName = new StringBuffer();
130 |
131 | String user = System.getProperty("user.name", "");
132 |
133 | if (user.length() > 31)
134 | user = user.substring(0, 31);
135 |
136 | this.userId = 0;
137 | this.groupId = 0;
138 | this.userName = new StringBuffer(user);
139 | this.groupName = new StringBuffer("");
140 | this.namePrefix = new StringBuffer();
141 | }
142 |
143 | /**
144 | * Parse an entry name from a header buffer.
145 | *
146 | * @param name
147 | * @param header
148 | * The header buffer from which to parse.
149 | * @param offset
150 | * The offset into the buffer from which to parse.
151 | * @param length
152 | * The number of header bytes to parse.
153 | * @return The header's entry name.
154 | */
155 | public static StringBuffer parseName(byte[] header, int offset, int length) {
156 | StringBuffer result = new StringBuffer(length);
157 |
158 | int end = offset + length;
159 | for (int i = offset; i < end; ++i) {
160 | if (header[i] == 0)
161 | break;
162 | result.append((char) header[i]);
163 | }
164 |
165 | return result;
166 | }
167 |
168 | /**
169 | * Determine the number of bytes in an entry name.
170 | *
171 | * @param name
172 | * @param header
173 | * The header buffer from which to parse.
174 | * @param offset
175 | * The offset into the buffer from which to parse.
176 | * @param length
177 | * The number of header bytes to parse.
178 | * @return The number of bytes in a header's entry name.
179 | */
180 | public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
181 | int i;
182 |
183 | for (i = 0; i < length && i < name.length(); ++i) {
184 | buf[offset + i] = (byte) name.charAt(i);
185 | }
186 |
187 | for (; i < length; ++i) {
188 | buf[offset + i] = 0;
189 | }
190 |
191 | return offset + length;
192 | }
193 |
194 | /**
195 | * Creates a new header for a file/directory entry.
196 | *
197 | *
198 | * @param name
199 | * File name
200 | * @param size
201 | * File size in bytes
202 | * @param modTime
203 | * Last modification time in numeric Unix time format
204 | * @param dir
205 | * Is directory
206 | *
207 | * @return
208 | */
209 | public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
210 | String name = entryName;
211 | name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
212 |
213 | TarHeader header = new TarHeader();
214 | header.linkName = new StringBuffer("");
215 |
216 | if (name.length() > 100) {
217 | header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
218 | header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
219 | } else {
220 | header.name = new StringBuffer(name);
221 | }
222 |
223 | if (dir) {
224 | header.mode = 040755;
225 | header.linkFlag = TarHeader.LF_DIR;
226 | if (header.name.charAt(header.name.length() - 1) != '/') {
227 | header.name.append("/");
228 | }
229 | header.size = 0;
230 | } else {
231 | header.mode = 0100644;
232 | header.linkFlag = TarHeader.LF_NORMAL;
233 | header.size = size;
234 | }
235 |
236 | header.modTime = modTime;
237 | header.checkSum = 0;
238 | header.devMajor = 0;
239 | header.devMinor = 0;
240 |
241 | return header;
242 | }
243 | }
--------------------------------------------------------------------------------
/lib/src/main/java/org/kamranzafar/jtar/TarInputStream.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 Kamran Zafar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package org.kamranzafar.jtar;
19 |
20 | import java.io.FilterInputStream;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 |
24 | /**
25 | * @author Kamran Zafar
26 | *
27 | */
28 | public class TarInputStream extends FilterInputStream {
29 |
30 | private static final int SKIP_BUFFER_SIZE = 2048;
31 | private TarEntry currentEntry;
32 | private long currentFileSize;
33 | private long bytesRead;
34 | private boolean defaultSkip = false;
35 |
36 | public TarInputStream(InputStream in) {
37 | super(in);
38 | currentFileSize = 0;
39 | bytesRead = 0;
40 | }
41 |
42 | @Override
43 | public boolean markSupported() {
44 | return false;
45 | }
46 |
47 | /**
48 | * Not supported
49 | *
50 | */
51 | @Override
52 | public synchronized void mark(int readlimit) {
53 | }
54 |
55 | /**
56 | * Not supported
57 | *
58 | */
59 | @Override
60 | public synchronized void reset() throws IOException {
61 | throw new IOException("mark/reset not supported");
62 | }
63 |
64 | /**
65 | * Read a byte
66 | *
67 | * @see FilterInputStream#read()
68 | */
69 | @Override
70 | public int read() throws IOException {
71 | byte[] buf = new byte[1];
72 |
73 | int res = this.read(buf, 0, 1);
74 |
75 | if (res != -1) {
76 | return 0xFF & buf[0];
77 | }
78 |
79 | return res;
80 | }
81 |
82 | /**
83 | * Checks if the bytes being read exceed the entry size and adjusts the byte
84 | * array length. Updates the byte counters
85 | *
86 | *
87 | * @see FilterInputStream#read(byte[], int, int)
88 | */
89 | @Override
90 | public int read(byte[] b, int off, int len) throws IOException {
91 | if (currentEntry != null) {
92 | if (currentFileSize == currentEntry.getSize()) {
93 | return -1;
94 | } else if ((currentEntry.getSize() - currentFileSize) < len) {
95 | len = (int) (currentEntry.getSize() - currentFileSize);
96 | }
97 | }
98 |
99 | int br = super.read(b, off, len);
100 |
101 | if (br != -1) {
102 | if (currentEntry != null) {
103 | currentFileSize += br;
104 | }
105 |
106 | bytesRead += br;
107 | }
108 |
109 | return br;
110 | }
111 |
112 | /**
113 | * Returns the next entry in the tar file
114 | *
115 | * @return TarEntry
116 | * @throws IOException
117 | */
118 | public TarEntry getNextEntry() throws IOException {
119 | closeCurrentEntry();
120 |
121 | byte[] header = new byte[TarConstants.HEADER_BLOCK];
122 | byte[] theader = new byte[TarConstants.HEADER_BLOCK];
123 | int tr = 0;
124 |
125 | // Read full header
126 | while (tr < TarConstants.HEADER_BLOCK) {
127 | int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
128 |
129 | if (res < 0) {
130 | break;
131 | }
132 |
133 | System.arraycopy(theader, 0, header, tr, res);
134 | tr += res;
135 | }
136 |
137 | // Check if record is null
138 | boolean eof = true;
139 | for (byte b : header) {
140 | if (b != 0) {
141 | eof = false;
142 | break;
143 | }
144 | }
145 |
146 | if (!eof) {
147 | currentEntry = new TarEntry(header);
148 | }
149 |
150 | return currentEntry;
151 | }
152 |
153 | /**
154 | * Returns the current offset (in bytes) from the beginning of the stream.
155 | * This can be used to find out at which point in a tar file an entry's content begins, for instance.
156 | */
157 | public long getCurrentOffset() {
158 | return bytesRead;
159 | }
160 |
161 | /**
162 | * Closes the current tar entry
163 | *
164 | * @throws IOException
165 | */
166 | protected void closeCurrentEntry() throws IOException {
167 | if (currentEntry != null) {
168 | if (currentEntry.getSize() > currentFileSize) {
169 | // Not fully read, skip rest of the bytes
170 | long bs = 0;
171 | while (bs < currentEntry.getSize() - currentFileSize) {
172 | long res = skip(currentEntry.getSize() - currentFileSize - bs);
173 |
174 | if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
175 | // I suspect file corruption
176 | throw new IOException("Possible tar file corruption");
177 | }
178 |
179 | bs += res;
180 | }
181 | }
182 |
183 | currentEntry = null;
184 | currentFileSize = 0L;
185 | skipPad();
186 | }
187 | }
188 |
189 | /**
190 | * Skips the pad at the end of each tar entry file content
191 | *
192 | * @throws IOException
193 | */
194 | protected void skipPad() throws IOException {
195 | if (bytesRead > 0) {
196 | int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
197 |
198 | if (extra > 0) {
199 | long bs = 0;
200 | while (bs < TarConstants.DATA_BLOCK - extra) {
201 | long res = skip(TarConstants.DATA_BLOCK - extra - bs);
202 | bs += res;
203 | }
204 | }
205 | }
206 | }
207 |
208 | /**
209 | * Skips 'n' bytes on the InputStream
210 | * Overrides default implementation of skip
211 | *
212 | */
213 | @Override
214 | public long skip(long n) throws IOException {
215 | if (defaultSkip) {
216 | // use skip method of parent stream
217 | // may not work if skip not implemented by parent
218 | long bs = super.skip(n);
219 | bytesRead += bs;
220 |
221 | return bs;
222 | }
223 |
224 | if (n <= 0) {
225 | return 0;
226 | }
227 |
228 | long left = n;
229 | byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
230 |
231 | while (left > 0) {
232 | int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
233 | if (res < 0) {
234 | break;
235 | }
236 | left -= res;
237 | }
238 |
239 | return n - left;
240 | }
241 |
242 | public boolean isDefaultSkip() {
243 | return defaultSkip;
244 | }
245 |
246 | public void setDefaultSkip(boolean defaultSkip) {
247 | this.defaultSkip = defaultSkip;
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/kamranzafar/jtar/TarOutputStream.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 Kamran Zafar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package org.kamranzafar.jtar;
19 |
20 | import java.io.BufferedOutputStream;
21 | import java.io.File;
22 | import java.io.FileNotFoundException;
23 | import java.io.FileOutputStream;
24 | import java.io.IOException;
25 | import java.io.OutputStream;
26 | import java.io.RandomAccessFile;
27 |
28 | /**
29 | * @author Kamran Zafar
30 | *
31 | */
32 | public class TarOutputStream extends OutputStream {
33 | private final OutputStream out;
34 | private long bytesWritten;
35 | private long currentFileSize;
36 | private TarEntry currentEntry;
37 |
38 | public TarOutputStream(OutputStream out) {
39 | this.out = out;
40 | bytesWritten = 0;
41 | currentFileSize = 0;
42 | }
43 |
44 | public TarOutputStream(final File fout) throws FileNotFoundException {
45 | this.out = new BufferedOutputStream(new FileOutputStream(fout));
46 | bytesWritten = 0;
47 | currentFileSize = 0;
48 | }
49 |
50 | /**
51 | * Opens a file for writing.
52 | */
53 | public TarOutputStream(final File fout, final boolean append) throws IOException {
54 | @SuppressWarnings("resource")
55 | RandomAccessFile raf = new RandomAccessFile(fout, "rw");
56 | final long fileSize = fout.length();
57 | if (append && fileSize > TarConstants.EOF_BLOCK) {
58 | raf.seek(fileSize - TarConstants.EOF_BLOCK);
59 | }
60 | out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
61 | }
62 |
63 | /**
64 | * Appends the EOF record and closes the stream
65 | *
66 | * @see java.io.FilterOutputStream#close()
67 | */
68 | @Override
69 | public void close() throws IOException {
70 | closeCurrentEntry();
71 | write( new byte[TarConstants.EOF_BLOCK] );
72 | out.close();
73 | }
74 | /**
75 | * Writes a byte to the stream and updates byte counters
76 | *
77 | * @see java.io.FilterOutputStream#write(int)
78 | */
79 | @Override
80 | public void write(int b) throws IOException {
81 | out.write( b );
82 | bytesWritten += 1;
83 |
84 | if (currentEntry != null) {
85 | currentFileSize += 1;
86 | }
87 | }
88 |
89 | /**
90 | * Checks if the bytes being written exceed the current entry size.
91 | *
92 | * @see java.io.FilterOutputStream#write(byte[], int, int)
93 | */
94 | @Override
95 | public void write(byte[] b, int off, int len) throws IOException {
96 | if (currentEntry != null && !currentEntry.isDirectory()) {
97 | if (currentEntry.getSize() < currentFileSize + len) {
98 | throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
99 | + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
100 | + "] being written." );
101 | }
102 | }
103 |
104 | out.write( b, off, len );
105 |
106 | bytesWritten += len;
107 |
108 | if (currentEntry != null) {
109 | currentFileSize += len;
110 | }
111 | }
112 |
113 | /**
114 | * Writes the next tar entry header on the stream
115 | *
116 | * @param entry
117 | * @throws IOException
118 | */
119 | public void putNextEntry(TarEntry entry) throws IOException {
120 | closeCurrentEntry();
121 |
122 | byte[] header = new byte[TarConstants.HEADER_BLOCK];
123 | entry.writeEntryHeader( header );
124 |
125 | write( header );
126 |
127 | currentEntry = entry;
128 | }
129 |
130 | /**
131 | * Closes the current tar entry
132 | *
133 | * @throws IOException
134 | */
135 | protected void closeCurrentEntry() throws IOException {
136 | if (currentEntry != null) {
137 | if (currentEntry.getSize() > currentFileSize) {
138 | throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
139 | + currentEntry.getSize() + "] has not been fully written." );
140 | }
141 |
142 | currentEntry = null;
143 | currentFileSize = 0;
144 |
145 | pad();
146 | }
147 | }
148 |
149 | /**
150 | * Pads the last content block
151 | *
152 | * @throws IOException
153 | */
154 | protected void pad() throws IOException {
155 | if (bytesWritten > 0) {
156 | int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
157 |
158 | if (extra > 0) {
159 | write( new byte[TarConstants.DATA_BLOCK - extra] );
160 | }
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/kamranzafar/jtar/TarUtils.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2012 Kamran Zafar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package org.kamranzafar.jtar;
19 |
20 | import java.io.File;
21 |
22 | /**
23 | * @author Kamran
24 | *
25 | */
26 | public class TarUtils {
27 | /**
28 | * Determines the tar file size of the given folder/file path
29 | *
30 | * @param path
31 | * @return
32 | */
33 | public static long calculateTarSize(File path) {
34 | return tarSize(path) + TarConstants.EOF_BLOCK;
35 | }
36 |
37 | private static long tarSize(File dir) {
38 | long size = 0;
39 |
40 | if (dir.isFile()) {
41 | return entrySize(dir.length());
42 | } else {
43 | File[] subFiles = dir.listFiles();
44 |
45 | if (subFiles != null && subFiles.length > 0) {
46 | for (File file : subFiles) {
47 | if (file.isFile()) {
48 | size += entrySize(file.length());
49 | } else {
50 | size += tarSize(file);
51 | }
52 | }
53 | } else {
54 | // Empty folder header
55 | return TarConstants.HEADER_BLOCK;
56 | }
57 | }
58 |
59 | return size;
60 | }
61 |
62 | private static long entrySize(long fileSize) {
63 | long size = 0;
64 | size += TarConstants.HEADER_BLOCK; // Header
65 | size += fileSize; // File size
66 |
67 | long extra = size % TarConstants.DATA_BLOCK;
68 |
69 | if (extra > 0) {
70 | size += (TarConstants.DATA_BLOCK - extra); // pad
71 | }
72 |
73 | return size;
74 | }
75 |
76 | public static String trim(String s, char c) {
77 | StringBuffer tmp = new StringBuffer(s);
78 | for (int i = 0; i < tmp.length(); i++) {
79 | if (tmp.charAt(i) != c) {
80 | break;
81 | } else {
82 | tmp.deleteCharAt(i);
83 | }
84 | }
85 |
86 | for (int i = tmp.length() - 1; i >= 0; i--) {
87 | if (tmp.charAt(i) != c) {
88 | break;
89 | } else {
90 | tmp.deleteCharAt(i);
91 | }
92 | }
93 |
94 | return tmp.toString();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/TaskWorkerThread.java:
--------------------------------------------------------------------------------
1 | package org.redwid.android.youtube.dl;
2 |
3 | import android.content.Context;
4 |
5 | import org.redwid.android.youtube.dl.unpack.UnpackTask;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Collections;
9 | import java.util.List;
10 |
11 | import timber.log.Timber;
12 |
13 | public class TaskWorkerThread extends Thread {
14 |
15 | public interface TaskWorkerThreadListener {
16 |
17 | void onCompleteAllItems();
18 | }
19 |
20 | private final List list = Collections.synchronizedList(new ArrayList());
21 |
22 | private Context context;
23 | private UnpackTask unpackTask;
24 |
25 | private boolean active = true;
26 | private Object taskInProgress = new Object();
27 |
28 | public TaskWorkerThread(final Context context) {
29 | this.context = context;
30 | start();
31 | }
32 |
33 | public void run() {
34 | while (active) {
35 | if (!list.isEmpty()) {
36 |
37 | final String stringUrl = list.get(0);
38 | Timber.i("performTask(), stringUrl: %s", stringUrl);
39 |
40 | final Thread thread = new Thread(new Runnable() {
41 | @Override
42 | public void run() {
43 | performTask(stringUrl);
44 | wakeUpTaskInProgress();
45 | }
46 | });
47 | thread.start();
48 |
49 | sleepTaskInProgress();
50 | }
51 | else {
52 | Timber.i("run() sleep");
53 | sleep();
54 | }
55 | }
56 | }
57 |
58 | private void sleepTaskInProgress() {
59 | Timber.i("sleepTaskInProgress() begin");
60 | synchronized (taskInProgress) {
61 | try {
62 | taskInProgress.wait(10000);
63 | } catch (InterruptedException e) {
64 | Timber.e(e, "ERROR in sleepTaskInProgress() interrupted");
65 | }
66 | }
67 | Timber.i("sleepTaskInProgress() done");
68 | }
69 |
70 | private void wakeUpTaskInProgress() {
71 | Timber.i("wakeUpTaskInProgress()");
72 | synchronized (taskInProgress) {
73 | taskInProgress.notify();
74 | }
75 | }
76 |
77 | public void cancel() {
78 | active = false;
79 | }
80 |
81 | public void add(final String valueUrl) {
82 | Timber.i("add(), list: %d", list.size());
83 | if(!list.contains(valueUrl)) {
84 | list.add(valueUrl);
85 | }
86 | wakeUp();
87 | }
88 |
89 | public synchronized void wakeUp() {
90 | Timber.i("wakeUp()");
91 | notify();
92 | }
93 |
94 | private synchronized void sleep() {
95 | try {
96 | Timber.i("sleep() begin ...");
97 | wait();
98 | Timber.i("sleep() done ...");
99 | } catch (InterruptedException e) {
100 | Timber.e(e,"ERROR in sleep()");
101 | }
102 | }
103 |
104 | private void performTask(final String stringUrl) {
105 | Timber.i("performTask()");
106 | try {
107 | if (unpackTask == null) {
108 | unpackTask = new UnpackTask();
109 | if (!unpackTask.unpack(context.getApplicationContext())) {
110 | unpackTask = null;
111 | }
112 | }
113 |
114 | final YoutubeDlWorker youtubeDlWorker = new YoutubeDlWorker();
115 | if (youtubeDlWorker.process(context.getApplicationContext(), stringUrl)) {
116 | Timber.i("performTask(), success");
117 | }
118 | list.remove(stringUrl);
119 | if (list.isEmpty()) {
120 | Timber.i("performTask(), list.isEmpty()");
121 | if (context instanceof TaskWorkerThreadListener) {
122 | Timber.i("performTask(), context instanceof TaskWorkerThreadListener");
123 | ((TaskWorkerThreadListener) context).onCompleteAllItems();
124 | }
125 | }
126 | }
127 | catch (Exception e) {
128 | Timber.e(e, "ERROR in performTask(%s)", stringUrl);
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/YoutubeDlService.java:
--------------------------------------------------------------------------------
1 | package org.redwid.android.youtube.dl;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationChannel;
5 | import android.app.NotificationManager;
6 | import android.app.Service;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.os.Binder;
10 | import android.os.Build;
11 | import android.os.IBinder;
12 |
13 | import androidx.core.app.NotificationCompat;
14 |
15 | import org.redwid.android.youtube.dl.TaskWorkerThread.TaskWorkerThreadListener;
16 | import org.redwid.youtube.dl.android.R;
17 |
18 | import timber.log.Timber;
19 |
20 | /**
21 | * Replace service to worker
22 | * https://medium.com/google-developer-experts/services-the-life-with-without-and-worker-6933111d62a6
23 | */
24 | public class YoutubeDlService extends Service implements TaskWorkerThreadListener {
25 |
26 | public static final String ACTION_DUMP_JSON = "org.redwid.android.youtube.dl.action.DUMP_JSON";
27 | public static final String JSON_RESULT_SUCCESS = "org.redwid.android.youtube.dl.result.JSON_RESULT_SUCCESS";
28 | public static final String JSON_RESULT_ERROR = "org.redwid.android.youtube.dl.result.JSON_RESULT_ERROR";
29 | public static final String VALUE_JSON = "JSON";
30 | public static final String VALUE_URL = "URL";
31 | public static final String VALUE_TIME_OUT = "TIME_OUT";
32 |
33 | public static final String NOTIFICATION_CHANNEL_ID = "youtube-dl-service";
34 | public static final int NOTIFICATION_ID = 111;
35 |
36 | private TaskWorkerThread taskWorkerThread;
37 | private NotificationManager notificationManager;
38 |
39 | private final IBinder binder = new LocalBinder();
40 |
41 | public class LocalBinder extends Binder {
42 | public YoutubeDlService getService()
43 | {
44 | return YoutubeDlService.this;
45 | }
46 | }
47 |
48 | public IBinder onBind(Intent intent)
49 | {
50 | return binder;
51 | }
52 |
53 | @Override
54 | public void onCreate() {
55 | super.onCreate();
56 | Timber.i("onCreate()");
57 | notificationManager = ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE));
58 | startForegroundIfNeeded();
59 | taskWorkerThread = new TaskWorkerThread(this);
60 | }
61 |
62 | @Override
63 | public void onStart(Intent intent, int startId) {
64 | super.onStart(intent, startId);
65 | Timber.i("onStart(%s, %d)", intent, startId);
66 | if(intent != null) {
67 | final String action = intent.getAction();
68 | final String valueUrl = intent.getStringExtra(VALUE_URL);
69 | Timber.i("onStart(), action: %s, valueUrl: %s", action, valueUrl);
70 | if (ACTION_DUMP_JSON.equals(action)) {
71 | taskWorkerThread.add(valueUrl);
72 | }
73 | } else {
74 | Timber.e("onStart() intent is null");
75 | }
76 | }
77 |
78 | public Notification getNotification(String title, String text) {
79 | return new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
80 | .setContentTitle(title)
81 | .setContentText(text)
82 | .build();
83 | }
84 |
85 | private void startForegroundIfNeeded() {
86 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
87 | final NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
88 | getString(R.string.notification_channel_name),
89 | NotificationManager.IMPORTANCE_DEFAULT);
90 | channel.setVibrationPattern(new long[]{0L});
91 | channel.enableVibration(true);
92 | notificationManager.createNotificationChannel(channel);
93 |
94 | startForeground(NOTIFICATION_ID, getNotification("", ""));
95 | }
96 | }
97 |
98 | @Override
99 | public void onDestroy() {
100 | Timber.i("onDestroy()");
101 | taskWorkerThread.cancel();
102 | taskWorkerThread = null;
103 | super.onDestroy();
104 | }
105 |
106 | @Override
107 | public void onCompleteAllItems() {
108 | Timber.i("onCompleteAllItems()");
109 | stopSelf();
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/YoutubeDlWorker.java:
--------------------------------------------------------------------------------
1 | package org.redwid.android.youtube.dl;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import org.redwid.android.youtube.dl.unpack.GZIPUtils;
7 |
8 | import java.io.BufferedReader;
9 | import java.io.File;
10 | import java.io.FileReader;
11 | import java.io.IOException;
12 |
13 | import timber.log.Timber;
14 |
15 | import static org.redwid.android.youtube.dl.YoutubeDlService.JSON_RESULT_ERROR;
16 | import static org.redwid.android.youtube.dl.YoutubeDlService.JSON_RESULT_SUCCESS;
17 | import static org.redwid.android.youtube.dl.YoutubeDlService.VALUE_JSON;
18 | import static org.redwid.android.youtube.dl.YoutubeDlService.VALUE_URL;
19 |
20 | /**
21 | * The YoutubeDlWorker class.
22 | */
23 | public class YoutubeDlWorker {
24 |
25 | public boolean process(final Context context, final String stringUrl) {
26 | Timber.i("process(%s)", this.hashCode());
27 |
28 | try {
29 | final long time = System.currentTimeMillis();
30 | final String cacheDir = context.getCacheDir().getAbsolutePath();
31 | final String applicationRootDir = context.getFilesDir().getAbsolutePath();
32 | final String pythonApplicationRootDir = applicationRootDir + "/youtube_dl";
33 | Timber.i("process(%s), stringUrl: %s", this.hashCode(), stringUrl);
34 |
35 | final String applicationArguments[] = new String[]{"app_process",
36 | "-j", stringUrl,
37 | "--cache-dir", cacheDir};
38 |
39 | final File dlDoneFile = new File(pythonApplicationRootDir, "youtube_dl.done");
40 | final File dlJsonFile = new File(pythonApplicationRootDir, "youtube_dl.json");
41 | if (dlDoneFile.exists()) {
42 | dlDoneFile.delete();
43 | }
44 | if (dlJsonFile.exists()) {
45 | dlJsonFile.delete();
46 | }
47 | loadNativeLibrary();
48 | final long startTime = System.currentTimeMillis();
49 | try {
50 | Timber.i("process(%s), nativeStart() begin", this.hashCode());
51 | nativeStart(applicationRootDir,
52 | pythonApplicationRootDir,
53 | "main.pyo",
54 | "python2.7",
55 | pythonApplicationRootDir,
56 | pythonApplicationRootDir + ":" + pythonApplicationRootDir + "/lib",
57 | applicationArguments);
58 | } catch (Throwable t) {
59 | Timber.e(t, "Exception in nativeStart()");
60 | broadcastFinishError(context, t, stringUrl);
61 | return false;
62 | }
63 | Timber.i("process(%s), nativeStart() end, time: %dms", this.hashCode(), System.currentTimeMillis() - startTime);
64 |
65 | Timber.i("process(%s), dlDoneFile.exists(): %b, dlJsonFile.exists(): %b", this.hashCode(), dlDoneFile.exists(), dlJsonFile.exists());
66 | if (dlJsonFile.exists()) {
67 | broadcastFinishSuccess(context, dlJsonFile, stringUrl);
68 | } else {
69 | broadcastFinishError(context, dlDoneFile, stringUrl);
70 | return false;
71 | }
72 | Timber.i("process(%s) end, t: %dms", this.hashCode(), System.currentTimeMillis() - time);
73 | } catch( Exception e) {
74 | Timber.e(e, "process(%s) InterruptedException", this.hashCode());
75 | return false;
76 | }
77 | return false;
78 | }
79 |
80 | private void broadcastFinishSuccess(final Context context, final File file, final String url) {
81 | //Timber.i("broadcastFinishSuccess()");
82 | final StringBuilder stringBuilder = readFile(file);
83 | if(stringBuilder.length() != 0) {
84 | sendBroadcast(context, url, JSON_RESULT_SUCCESS, stringBuilder);
85 | }
86 | else {
87 | broadcastFinishError(context, new IOException("Unable to read file: " + file.getName()), url);
88 | }
89 | }
90 |
91 | private void broadcastFinishError(final Context context, final File file, final String url) {
92 | //Timber.i("broadcastFinishError()");
93 | final StringBuilder stringBuilder = readFile(file);
94 | if(stringBuilder.length() != 0) {
95 | sendBroadcast(context, url, JSON_RESULT_ERROR, stringBuilder);
96 | }
97 | }
98 |
99 | private void broadcastFinishError(final Context context, final Throwable throwable, final String url) {
100 | //Timber.i("broadcastFinishError()");
101 | final StringBuilder stringBuilder = new StringBuilder();
102 | stringBuilder.append("{\"result\":\"");
103 | stringBuilder.append(throwable);
104 | stringBuilder.append("\"}");
105 | if(stringBuilder.length() != 0) {
106 | sendBroadcast(context, url, JSON_RESULT_ERROR, stringBuilder);
107 | }
108 | }
109 |
110 | private void sendBroadcast(final Context context, final String url, final String action, final StringBuilder stringBuilder) {
111 | Timber.i("sendBroadcast(%s), string length: %d", url, stringBuilder.length());
112 | try {
113 | final Intent jsonIntent = new Intent(action);
114 | jsonIntent.putExtra(VALUE_URL, url);
115 | jsonIntent.putExtra(VALUE_JSON, GZIPUtils.compress(stringBuilder.toString()));
116 | context.sendBroadcast(jsonIntent);
117 | } catch(Throwable t) {
118 | Timber.e(t, "Exception in broadcastFinishError()");
119 | }
120 | }
121 |
122 | private StringBuilder readFile(final File file) {
123 | //Timber.i("readFile(), file: %s", file);
124 | final StringBuilder stringBuilder = new StringBuilder();
125 | if(file != null && file.exists()) {
126 | BufferedReader bufferedReader = null;
127 | try {
128 | bufferedReader = new BufferedReader(new FileReader(file));
129 | String line = bufferedReader.readLine();
130 | while (line != null) {
131 | stringBuilder.append(line);
132 | stringBuilder.append("\n");
133 | line = bufferedReader.readLine();
134 | }
135 | } catch (Exception e) {
136 | Timber.e(e, "Exception in readFile()");
137 | } finally {
138 | try {
139 | bufferedReader.close();
140 | } catch (IOException e) {
141 | e.printStackTrace();
142 | }
143 | }
144 | } else {
145 | stringBuilder.append("{\"result\":\"");
146 | stringBuilder.append("Error file is not exist: ").append(file.getAbsolutePath());
147 | stringBuilder.append("\"}");
148 | }
149 |
150 | if (stringBuilder.length() == 0) {
151 | stringBuilder.append("{\"result\":\"");
152 | stringBuilder.append("Unknown error");
153 | stringBuilder.append("\"}");
154 | }
155 |
156 | //Timber.i("readFile(), stringBuilder: %s", stringBuilder);
157 | return stringBuilder;
158 | }
159 |
160 | private void loadNativeLibrary() {
161 | Timber.i("loadNativeLibrary(%s)", this.hashCode());
162 | final String main = "main";
163 | try {
164 | System.loadLibrary(main);
165 | Timber.i("loadNativeLibrary(%s), loaded: lib%s.so", this.hashCode(), main);
166 | } catch(UnsatisfiedLinkError e) {
167 | Timber.e(e, "UnsatisfiedLinkError in loadNativeLibrary(), can't load: %s", main);
168 | } catch(Exception e) {
169 | Timber.e(e, "Exception in loadNativeLibrary(), can't load: %s", main);
170 | }
171 | }
172 |
173 | // Native part
174 | public static native void nativeStart(String androidPrivate,
175 | String androidArgument,
176 | String applicationEntrypoint,
177 | String pythonName,
178 | String pythonHome,
179 | String pythonPath,
180 | String applicationArguments[]);
181 | }
182 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/unpack/AssetExtract.java:
--------------------------------------------------------------------------------
1 | // This string is autogenerated by ChangeAppSettings.sh, do not change
2 | // spaces amount
3 | package org.redwid.android.youtube.dl.unpack;
4 |
5 | import android.content.Context;
6 | import android.content.res.AssetManager;
7 |
8 | import java.io.BufferedInputStream;
9 | import java.io.BufferedOutputStream;
10 | import java.io.File;
11 | import java.io.FileNotFoundException;
12 | import java.io.FileOutputStream;
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.io.OutputStream;
16 | import java.util.zip.GZIPInputStream;
17 |
18 | import org.kamranzafar.jtar.TarEntry;
19 | import org.kamranzafar.jtar.TarInputStream;
20 |
21 | import timber.log.Timber;
22 |
23 | public class AssetExtract {
24 |
25 | private AssetManager assetManager = null;
26 |
27 | public AssetExtract(Context context) {
28 | this.assetManager = context.getAssets();
29 | }
30 |
31 | public boolean extractTar(String asset, String target) {
32 |
33 | byte buf[] = new byte[1024 * 1024];
34 |
35 | InputStream assetStream = null;
36 | TarInputStream tis = null;
37 |
38 | try {
39 | assetStream = assetManager.open(asset, AssetManager.ACCESS_STREAMING);
40 | tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
41 | } catch (IOException e) {
42 | Timber.e(e, "Exception in extractTar(), opening up extract tar");
43 | return false;
44 | }
45 |
46 | while (true) {
47 | TarEntry entry = null;
48 |
49 | try {
50 | entry = tis.getNextEntry();
51 | } catch ( IOException e ) {
52 | Timber.e( e, "Exception in extractTar(), extracting tar");
53 | return false;
54 | }
55 |
56 | if ( entry == null ) {
57 | break;
58 | }
59 |
60 | Timber.i("extractTar(), extracting %s", entry.getName());
61 |
62 | if (entry.isDirectory()) {
63 |
64 | try {
65 | new File(target +"/" + entry.getName()).mkdirs();
66 | } catch ( SecurityException e ) { };
67 |
68 | continue;
69 | }
70 |
71 | OutputStream out = null;
72 | String path = target + "/" + entry.getName();
73 |
74 | try {
75 | out = new BufferedOutputStream(new FileOutputStream(path), 8192);
76 | } catch ( FileNotFoundException e ) {
77 | } catch ( SecurityException e ) { };
78 |
79 | if ( out == null ) {
80 | Timber.e("Exception in extractTar(), could not open %s", path);
81 | return false;
82 | }
83 |
84 | try {
85 | while (true) {
86 | int len = tis.read(buf);
87 |
88 | if (len == -1) {
89 | break;
90 | }
91 |
92 | out.write(buf, 0, len);
93 | }
94 |
95 | out.flush();
96 | out.close();
97 | } catch ( IOException e ) {
98 | Timber.e(e, "Exception in extractTar()");
99 | return false;
100 | }
101 | }
102 |
103 | try {
104 | tis.close();
105 | assetStream.close();
106 | } catch (IOException e) {
107 | // pass
108 | }
109 |
110 | return true;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/unpack/GZIPUtils.java:
--------------------------------------------------------------------------------
1 | package org.redwid.android.youtube.dl.unpack;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.ByteArrayInputStream;
7 | import java.io.ByteArrayOutputStream;
8 | import java.io.InputStreamReader;
9 | import java.util.zip.GZIPInputStream;
10 | import java.util.zip.GZIPOutputStream;
11 |
12 | import timber.log.Timber;
13 |
14 | public class GZIPUtils {
15 |
16 | public static final String UTF_8 = "UTF-8";
17 |
18 | public static byte[] compress(final String stringToCompress) {
19 | final long time = System.currentTimeMillis();
20 | byte[] result = new byte[0];
21 | if (!TextUtils.isEmpty(stringToCompress)) {
22 | try {
23 | final ByteArrayOutputStream baos = new ByteArrayOutputStream();
24 | final GZIPOutputStream gzipOutput = new GZIPOutputStream(baos);
25 | gzipOutput.write(stringToCompress.getBytes(UTF_8));
26 | gzipOutput.finish();
27 | result = baos.toByteArray();
28 | } catch (Exception e) {
29 | Timber.e(e, "ERROR in compress()");
30 | }
31 | }
32 | Timber.d("compress(), t: %dms", System.currentTimeMillis() - time);
33 | return result;
34 | }
35 |
36 | public static String uncompress(final byte[] compressed) {
37 | final long time = System.currentTimeMillis();
38 | final StringBuilder stringBuilder = new StringBuilder();
39 | if (compressed != null && compressed.length > 0) {
40 |
41 | if (isCompressed(compressed)) {
42 | try {
43 | final GZIPInputStream gzipInput = new GZIPInputStream(new ByteArrayInputStream(compressed));
44 | final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gzipInput, UTF_8));
45 |
46 | String line;
47 | while ((line = bufferedReader.readLine()) != null) {
48 | stringBuilder.append(line);
49 | }
50 | bufferedReader.close();
51 | gzipInput.close();
52 | } catch (Exception e) {
53 | Timber.e(e, "ERROR in uncompress()");
54 | }
55 | } else {
56 | stringBuilder.append(compressed);
57 | }
58 | }
59 | Timber.d("uncompress(), t: %dms", System.currentTimeMillis() - time);
60 | return stringBuilder.toString();
61 | }
62 |
63 | public static boolean isCompressed(final byte[] compressed) {
64 | return (compressed[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (compressed[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/unpack/ResourceManager.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This class takes care of managing resources for us. In our code, we
3 | * can't use R, since the name of the package containing R will
4 | * change. (This same code is used in both org.renpy.android and
5 | * org.renpy.pygame.) So this is the next best thing.
6 | */
7 |
8 | package org.redwid.android.youtube.dl.unpack;
9 |
10 | import android.content.Context;
11 | import android.content.res.Resources;
12 |
13 | import timber.log.Timber;
14 |
15 | public class ResourceManager {
16 |
17 | private Context context;
18 | private Resources resources;
19 |
20 | public ResourceManager(Context context) {
21 | this.context = context;
22 | this.resources = context.getResources();
23 | }
24 |
25 | public int getIdentifier(String name, String kind) {
26 | Timber.i("getIdentifier(%s, %s)", name, kind);
27 | final int value = resources.getIdentifier(name, kind, context.getPackageName());
28 | Timber.i("getIdentifier(), value: %d", value);
29 | return value;
30 | }
31 |
32 | public String getString(String name) {
33 | try {
34 | Timber.i("getString(%s)", name);
35 | return resources.getString(getIdentifier(name, "string"));
36 | } catch (Exception e) {
37 | Timber.e(e, "Exception in getString(%s)", name);
38 | return null;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/unpack/UnpackFilesTask.java:
--------------------------------------------------------------------------------
1 | package org.redwid.android.youtube.dl.unpack;
2 |
3 | import android.content.Context;
4 | import android.os.AsyncTask;
5 | import android.os.Handler;
6 | import android.widget.Toast;
7 |
8 | import java.io.File;
9 | import java.io.FileInputStream;
10 | import java.io.FileOutputStream;
11 | import java.io.InputStream;
12 |
13 | import timber.log.Timber;
14 |
15 | /**
16 | * The UnpackFilesTask class.
17 | */
18 | public class UnpackFilesTask extends AsyncTask {
19 |
20 | private Context context = null;
21 | private ResourceManager resourceManager = null;
22 | private AssetExtract assetExtract = null;
23 | private UnpackFilesTaskCallback unpackFilesTaskCallback;
24 |
25 | private final Handler handler = new Handler();
26 |
27 | public UnpackFilesTask(final Context context, final UnpackFilesTaskCallback unpackFilesTaskCallback) {
28 | Timber.i("UnpackFilesTask ");
29 | this.context = context;
30 | this.resourceManager = new ResourceManager(context);
31 | this.assetExtract = new AssetExtract(context);
32 | this.unpackFilesTaskCallback = unpackFilesTaskCallback;
33 | }
34 |
35 | @Override
36 | protected Void doInBackground(Void... params) {
37 | Timber.i( "Ready to unpack");
38 | unpackData("private", new File(getAppRoot()));
39 | return null;
40 | }
41 |
42 | @Override
43 | protected void onPostExecute(Void result) {
44 | unpackFilesTaskCallback.onPostExecute();
45 | }
46 |
47 | public String getAppRoot() {
48 | String app_root = context.getFilesDir().getAbsolutePath() + "/youtube_dl";
49 | return app_root;
50 | }
51 |
52 | public void unpackData(final String resource, File target) {
53 | Timber.i( "unpackData(%s, %s)", resource, target.getName());
54 |
55 | // The version of data in memory and on disk.
56 | String data_version = resourceManager.getString(resource + "_version");
57 | String disk_version = null;
58 |
59 | Timber.i( "Data version is %s", data_version);
60 |
61 | // If no version, no unpacking is necessary.
62 | if (data_version == null) {
63 | return;
64 | }
65 |
66 | // Check the current disk version, if any.
67 | String filesDir = target.getAbsolutePath();
68 | String disk_version_fn = filesDir + "/" + resource + ".version";
69 |
70 | try {
71 | byte buf[] = new byte[64];
72 | InputStream is = new FileInputStream(disk_version_fn);
73 | int len = is.read(buf);
74 | disk_version = new String(buf, 0, len);
75 | is.close();
76 | } catch (Exception e) {
77 | disk_version = "";
78 | }
79 |
80 | // If the disk data is out of date, extract it and write the
81 | // version file.
82 | // if (! data_version.equals(disk_version)) {
83 | if (! data_version.equals(disk_version)) {
84 | Timber.i( "Extracting %s assets.", resource);
85 |
86 | recursiveDelete(target);
87 | target.mkdirs();
88 |
89 | if (!assetExtract.extractTar(resource + ".mp3", target.getAbsolutePath())) {
90 | toastError("Could not extract " + resource + " data.");
91 | }
92 |
93 | try {
94 | // Write .nomedia.
95 | new File(target, ".nomedia").createNewFile();
96 |
97 | // Write version file.
98 | FileOutputStream os = new FileOutputStream(disk_version_fn);
99 | os.write(data_version.getBytes());
100 | os.close();
101 | } catch (Exception e) {
102 | Timber.e(e, "Exception in unpackData()");
103 | }
104 | }
105 | }
106 |
107 | public void recursiveDelete(File f) {
108 | if (f.isDirectory()) {
109 | for (File r : f.listFiles()) {
110 | recursiveDelete(r);
111 | }
112 | }
113 | f.delete();
114 | }
115 |
116 | /**
117 | * Show an error using a toast. (Only makes sense from non-UI
118 | * threads.)
119 | */
120 | public void toastError(final String msg) {
121 |
122 | handler.post(new Runnable () {
123 | public void run() {
124 | Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
125 | }
126 | });
127 |
128 | // Wait to show the error.
129 | synchronized (this) {
130 | try {
131 | this.wait(1000);
132 | } catch (InterruptedException e) {
133 | }
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/unpack/UnpackFilesTaskCallback.java:
--------------------------------------------------------------------------------
1 | package org.redwid.android.youtube.dl.unpack;
2 |
3 | /**
4 | * The UnpackFilesTaskCallback class.
5 | */
6 | public interface UnpackFilesTaskCallback {
7 |
8 | void onPostExecute();
9 | }
10 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/redwid/android/youtube/dl/unpack/UnpackTask.java:
--------------------------------------------------------------------------------
1 | package org.redwid.android.youtube.dl.unpack;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.text.TextUtils;
6 |
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.FileOutputStream;
10 | import java.io.InputStream;
11 |
12 | import timber.log.Timber;
13 |
14 | /**
15 | * The UnpackTask class.
16 | */
17 | public class UnpackTask {
18 |
19 | public static final String PRIVATE = "private";
20 | public static final String YOUTUBE_DL = "youtube_dl";
21 |
22 | public boolean unpack(final Context context) {
23 | return unpackData(PRIVATE, getAppRootFile(context), context);
24 | }
25 |
26 | private File getAppRootFile(final Context context) {
27 | final String app_root = context.getFilesDir().getAbsolutePath() + "/" + YOUTUBE_DL;
28 | return new File(app_root);
29 | }
30 |
31 | private boolean unpackData(final String resource, final File target, final Context applicationContext) {
32 | Timber.i( "unpackData(%s, %s)", resource, target.getName());
33 | final long time = System.currentTimeMillis();
34 |
35 | final ResourceManager resourceManager = new ResourceManager(applicationContext);
36 | final AssetExtract assetExtract = new AssetExtract(applicationContext);
37 |
38 | // The version of data in memory and on disk.
39 | String data_version = resourceManager.getString(resource + "_version");
40 | String disk_version = null;
41 |
42 | Timber.i( "Data version is %s", data_version);
43 |
44 | // If no version, no unpacking is necessary.
45 | if (data_version == null) {
46 | return true;
47 | }
48 |
49 | // Check the current disk version, if any.
50 | String filesDir = target.getAbsolutePath();
51 | String disk_version_fn = filesDir + "/" + resource + ".version";
52 |
53 | try {
54 | byte buf[] = new byte[64];
55 | InputStream is = new FileInputStream(disk_version_fn);
56 | int len = is.read(buf);
57 | disk_version = new String(buf, 0, len);
58 | is.close();
59 | } catch (Exception e) {
60 | disk_version = "";
61 | }
62 |
63 | // If the disk data is out of date, extract it and write the
64 | // version file.
65 | // if (! data_version.equals(disk_version)) {
66 | if (! data_version.equals(disk_version)) {
67 | Timber.i( "Extracting %s assets", resource);
68 |
69 | recursiveDelete(target);
70 | target.mkdirs();
71 |
72 | final String cpuABI = getCPU_ABI();
73 | Timber.d( "cpuABI: %s", cpuABI);
74 | if (!assetExtract.extractTar(resource + "-" + cpuABI + ".mp3", target.getAbsolutePath())) {
75 | //toastError("Could not extract " + resource + " data.");
76 | Timber.e( "Could not extract %s data for cpu abi: %s", resource, cpuABI);
77 | }
78 |
79 | try {
80 | // Write .nomedia.
81 | new File(target, ".nomedia").createNewFile();
82 |
83 | // Write version file.
84 | FileOutputStream os = new FileOutputStream(disk_version_fn);
85 | os.write(data_version.getBytes());
86 | os.close();
87 | } catch (Exception e) {
88 | Timber.e(e, "Exception in unpackData()");
89 | return false;
90 | }
91 | }
92 | Timber.i( "unpackData() done, t: %dms", System.currentTimeMillis() - time);
93 | return true;
94 | }
95 |
96 | private String getCPU_ABI() {
97 | if(!TextUtils.isEmpty(Build.CPU_ABI)) {
98 | return Build.CPU_ABI;
99 | }
100 | if(!TextUtils.isEmpty(Build.CPU_ABI2)) {
101 | return Build.CPU_ABI2;
102 | }
103 | return "";
104 | }
105 |
106 | public void recursiveDelete(File f) {
107 | if (f.isDirectory()) {
108 | for (File r : f.listFiles()) {
109 | recursiveDelete(r);
110 | }
111 | }
112 | f.delete();
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/lib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | lib
3 | 08_04_2020
4 | Youtube-Dl Service
5 |
6 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # android-youtube-dl
2 | The android library that wraps Python 2.7 and youtube-dl python scripts.
3 |
4 | The library uses Python 2.7 distribution from: https://github.com/kivy/python-for-android project
5 |
6 | The forked version of python-for-android could be found there:
7 | https://github.com/Redwid/python-for-android
8 | It has modified native wrapper to work with ``org.redwid.android.youtube.dl.YoutubeDlService``
9 |
10 | The forked version of python youtube-dl application could be found there:
11 | https://github.com/Redwid/youtube-dl
12 | It has a few changes to be able to work on android with Python 2.7, in additional it writes the output into files instead of printing to console.
13 |
14 | ## Build Status
15 | [](https://travis-ci.com/Redwid/android-youtube-dl)
16 |
17 | ## Usage
18 | You could embed that library into your own android application (please see /app as an example).
19 |
20 | Or install the app and invoke it by using intents:
21 |
22 | Firstly add this constants to your code:
23 | ```java
24 | public static final String ACTION_DUMP_JSON = "org.redwid.android.youtube.dl.action.DUMP_JSON";
25 | public static final String JSON_RESULT_SUCCESS = "org.redwid.android.youtube.dl.result.JSON_RESULT_SUCCESS";
26 | public static final String JSON_RESULT_ERROR = "org.redwid.android.youtube.dl.result.JSON_RESULT_ERROR";
27 | public static final String VALUE_JSON = "JSON";
28 | public static final String VALUE_URL = "URL";
29 | public static final String VALUE_TIME_OUT = "TIME_OUT";
30 | ```
31 | Register broadcast receiver:
32 | ```java
33 | final IntentFilter intentFilter = new IntentFilter();
34 | intentFilter.addAction(JSON_RESULT_SUCCESS);
35 | intentFilter.addAction(JSON_RESULT_ERROR);
36 | this.context.registerReceiver(new BroadcastReceiver() {
37 | @Override
38 | public void onReceive(final Context context, final Intent intent) {
39 | if(JSON_RESULT_SUCCESS.equals(intent.getAction()) || JSON_RESULT_ERROR.equals(intent.getAction())) {
40 | if (JSON_RESULT_ERROR.equals(intent.getAction())) {
41 | Log.d(TAG, "onReceive(): JSON_RESULT_ERROR");
42 | } else if (JSON_RESULT_SUCCESS.equals(intent.getAction())) {
43 | Log.d(TAG, "onReceive(): JSON_RESULT_SUCCESS");
44 | final String jsonAsString = intent.getStringExtra(VALUE_JSON);
45 | //Paase youtube-dl json response
46 | }
47 | }
48 | }
49 | }, intentFilter);
50 | ```
51 | And finally send intent to android-youtube-dl, where %url% is a link to your youtube video:
52 | ```java
53 | final Intent intent = new Intent();
54 | intent.setClassName("org.redwid.android.youtube.dl.app", "org.redwid.android.youtube.dl.YoutubeDlService");
55 | intent.setAction(ACTION_DUMP_JSON);
56 | intent.putExtra(VALUE_URL, %url%);
57 | context.startService(serviceIntent);
58 | ```
59 | When ``YoutubeDlService`` finished, your application will receive ``JSON_RESULT_SUCCESS`` or ``JSON_RESULT_ERROR`` intents.
60 |
61 | ## Update
62 | If you would like to customize youtube-dl python scripts or android native wrapper - re build private.mp3 and jniLibs:
63 | 1. Clone youtube-dl:
64 | git clone https://github.com/Redwid/youtube-dl.git
65 | 2. Clone python-for-android:
66 | git clone https://github.com/Redwid/python-for-android.git
67 | 3. Execute sh /python-for-android/clean-build-copy.sh
68 | 4. Find you new python and youtube-dl package in /libs/src/main/assets/private.mp3
69 | and native libraries in /libs/src/main/jniLibs/
70 |
71 | ## App
72 | You could use the sample application to find out how to use *android-youtube-dl*
73 | Application has one screen.
74 | If you share any video from youtube android application to *Android Youtube-Dl*, after processing it will display video meta info and all discovered links:
75 |
76 |  
77 |
78 | ## License
79 | [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
80 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':lib'
2 |
--------------------------------------------------------------------------------