Skip to content

Commit 4b86db0

Browse files
committed
[duplicated subtitle] Support downloading local subtitles after deduplication (SubtitleDeduplicator)
- Introduce `SubtitleDeduplicator` into NewPipeExtractor; subtitles are now cached locally. - Add support for `file://` protocol to process local subtitle files (e.g., file:///storage/emulated/0/Android/data/<package_name>/cache/subtitle_cache/<cached_TTML_subtitle_name>) instead of remote URLs. - Separate handling of local and remote subtitle in `run()` for better clarity. Note: After calling `SubtitleDeduplicator.checkAndDeduplicate()`, all YouTube subtitles are local. - Clarify the early return logic for single-URL subtitle download.
1 parent f99d53a commit 4b86db0

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

app/src/main/java/us/shandian/giga/get/DownloadInitializer.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
import java.io.InterruptedIOException;
1212
import java.net.HttpURLConnection;
1313
import java.nio.channels.ClosedByInterruptException;
14+
import java.io.File;
15+
import java.io.FileInputStream;
16+
import java.io.FileOutputStream;
1417

1518
import us.shandian.giga.util.Utility;
1619

1720
import static org.schabi.newpipe.BuildConfig.DEBUG;
1821
import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN;
22+
import org.schabi.newpipe.extractor.utils.SubtitleDeduplicator;
1923

2024
public class DownloadInitializer extends Thread {
2125
private static final String TAG = "DownloadInitializer";
@@ -46,6 +50,23 @@ public void run() {
4650
int retryCount = 0;
4751
int httpCode = 204;
4852

53+
//process local file URI (file://)
54+
for (int i = 0; i < mMission.urls.length && mMission.running; i++) {
55+
String currentUrl = mMission.urls[i];
56+
57+
if (true == islocalSubtitleUri(currentUrl)) {
58+
LocalSubtitleConverter.convertLocalTtmlToVtt(currentUrl, mMission);
59+
60+
// Subtitle download missions always contain exactly one URL.
61+
// Once the local subtitle is converted, the mission is
62+
// considered finished.
63+
// Do not replace this with `continue` unless subtitle missions
64+
// support multiple URLs in the future.
65+
return;
66+
}
67+
}
68+
69+
// process remote, for example: http:// or https://
4970
while (true) {
5071
try {
5172
if (mMission.blocks == null && mMission.current == 0) {
@@ -54,6 +75,7 @@ public void run() {
5475
long lowestSize = Long.MAX_VALUE;
5576

5677
for (int i = 0; i < mMission.urls.length && mMission.running; i++) {
78+
5779
mConn = mMission.openConnection(mMission.urls[i], true, 0, 0);
5880
mMission.establishConnection(mId, mConn);
5981
dispose();
@@ -205,4 +227,34 @@ public void interrupt() {
205227
super.interrupt();
206228
if (mConn != null) dispose();
207229
}
230+
231+
private boolean isLocalUri(String url) {
232+
String URL_PREFIX = SubtitleDeduplicator.LOCAL_SUBTITLE_URL_PREFIX;
233+
234+
if (url.startsWith(URL_PREFIX)) {
235+
return true;
236+
} else {
237+
return false;
238+
}
239+
}
240+
241+
private boolean isSubtitleDownloadMission() {
242+
char downloadKind = mMission.kind;
243+
if ('s' == downloadKind) {
244+
return true;
245+
}
246+
247+
return false;
248+
}
249+
250+
private boolean islocalSubtitleUri(String url) {
251+
if (true == isSubtitleDownloadMission()) {
252+
if (true == isLocalUri(url)) {
253+
return true;
254+
}
255+
}
256+
257+
return false;
258+
}
259+
208260
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package us.shandian.giga.get;
2+
3+
import java.io.File;
4+
import java.io.FileInputStream;
5+
import java.io.FileOutputStream;
6+
import java.io.IOException;
7+
import java.io.InterruptedIOException;
8+
import android.util.Log;
9+
10+
import org.schabi.newpipe.streams.io.SharpStream;
11+
12+
import org.schabi.newpipe.extractor.utils.SubtitleDeduplicator;
13+
14+
final class LocalSubtitleConverter {
15+
16+
private static final String TAG = "LocalSubtitleConverter";
17+
18+
private LocalSubtitleConverter() {
19+
// no instance
20+
}
21+
22+
/**
23+
* Converts a local(file://) TTML subtitle file into VTT format
24+
* and stores it in the user-defined/chosen directory.
25+
*
26+
* @param localSubtitleUri file:// URI of the local TTML subtitle
27+
* @param subtitleMission current download mission, and it is a command
28+
* initiated manually by the user (via a button).
29+
* @return 0 if success, non-zero error code otherwise
30+
*/
31+
public static int convertLocalTtmlToVtt(String localSubtitleUri,
32+
DownloadMission subtitleMission) {
33+
34+
if (!isValidLocalUri(localSubtitleUri)) {
35+
return 1;
36+
}
37+
38+
File ttmlFile = new File(
39+
getAbsolutePathFromLocalUri(localSubtitleUri)
40+
);
41+
42+
if (!ttmlFile.exists()) {
43+
subtitleMission.notifyError(DownloadMission.ERROR_FILE_CREATION, null);
44+
return 2;
45+
}
46+
47+
if (!subtitleMission.storage.canWrite()) {
48+
subtitleMission.notifyError(DownloadMission.ERROR_PERMISSION_DENIED, null);
49+
return 3;
50+
}
51+
52+
writeLocalTtmlAsVtt(ttmlFile, subtitleMission);
53+
54+
printLocalSubtitleConvertedOk(subtitleMission);
55+
56+
return 0;
57+
}
58+
59+
60+
private static boolean isValidLocalUri(String localUri) {
61+
String URL_PREFIX = SubtitleDeduplicator.LOCAL_SUBTITLE_URL_PREFIX;
62+
63+
if (localUri.length() <= URL_PREFIX.length()) {
64+
return false;
65+
}
66+
67+
return true;
68+
}
69+
70+
private static String getAbsolutePathFromLocalUri(String localSubtitleUri) {
71+
String URL_PREFIX = SubtitleDeduplicator.LOCAL_SUBTITLE_URL_PREFIX;
72+
int prefixLength = URL_PREFIX.length();
73+
// Remove URL_PREFIX
74+
String absolutePath = localSubtitleUri.substring(prefixLength);
75+
return absolutePath;
76+
}
77+
78+
private static void writeLocalTtmlAsVtt(File localTtmlFile,
79+
DownloadMission mission) {
80+
try (FileInputStream inputTtmlStream = new FileInputStream(localTtmlFile);
81+
SharpStream outputVttStream = mission.storage.getStream()) {
82+
83+
byte[] buffer = new byte[DownloadMission.BUFFER_SIZE];
84+
int bytesRead;
85+
long totalBytes = 0;
86+
87+
while ((bytesRead = inputTtmlStream.read(buffer)) != -1) {
88+
outputVttStream.write(buffer, 0, bytesRead);
89+
totalBytes += bytesRead;
90+
mission.notifyProgress(bytesRead);
91+
}
92+
93+
mission.length = totalBytes;
94+
mission.unknownLength = false;
95+
mission.notifyFinished();
96+
97+
} catch (IOException e) {
98+
String logMessage = "Error extracting subtitle paragraphs from " +
99+
localTtmlFile.getAbsolutePath() + ", error:" +
100+
e.getMessage();
101+
Log.e(TAG, logMessage);
102+
mission.notifyError(DownloadMission.ERROR_FILE_CREATION, e);
103+
}
104+
}
105+
106+
private static void printLocalSubtitleConvertedOk(DownloadMission mission) {
107+
try {
108+
String logMessage = "Local subtitle uri is extracted to:" +
109+
mission.storage.getName();
110+
Log.i(TAG, logMessage);
111+
} catch (NullPointerException e) {
112+
Log.w(TAG, "Fail to convert ttml subtitle to vtt.", e);
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)