Skip to content

Commit

Permalink
feat: init Android setup & main activity (cpws)
Browse files Browse the repository at this point in the history
* **Build Configuration**
  - Add `android/build.gradle` for project-level build configuration
  - Add `android/app/build.gradle` for app module build configuration

* **Main Activity and Layout**
  - Add `MainActivity.java` for handling audio and video file selection and transcription
  - Add `activity_main.xml` for main activity layout
  - Add `AndroidManifest.xml` for app configuration

* **Resources**
  - Add `strings.xml` for string resources
  - Add `styles.xml` for app theme

* **Transcription Service and Utilities**
  - Add `TranscriptionService.java` for handling transcription tasks
  - Add `AudioUtils.java` and `VideoUtils.java` for audio and video file handling
  - Add `TranscriptionUtils.java` for transcription conversion and saving

* **File Picker Fragment**
  - Add `FilePickerFragment.java` for selecting audio/video files
  - Add `fragment_file_picker.xml` for file picker fragment layout

* **Real-Time ASR Fragment**
  - Add `RealTimeASRFragment.java` for real-time transcription
  - Add `fragment_real_time_asr.xml` for real-time ASR fragment layout

* **Transcription Display**
  - Add `TranscriptionAdapter.java` for displaying transcription items
  - Add `item_transcription.xml` for transcription item layout

* **Documentation**
  - Update `README.md` with instructions on building and running the Android app

* **CI Configuration**
  - Add `.github/workflows/android.yml` for GitHub Actions workflow to generate APK on every commit
  • Loading branch information
tribixbite committed Oct 23, 2024
1 parent 021e394 commit 575ed6e
Show file tree
Hide file tree
Showing 19 changed files with 812 additions and 0 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Android CI

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 11

- name: Build with Gradle
run: ./gradlew build

- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: app-release.apk
path: android/app/build/outputs/apk/release/app-release.apk
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,23 @@ python

The first argument is a path to an audio file and the second is the name of a Moonshine model. `moonshine/tiny` and `moonshine/base` are the currently available models.

## Building and Running the Android App

### Prerequisites

- Android Studio installed on your machine.
- Android device or emulator for testing.

### Steps

1. Open Android Studio and select "Open an existing Android Studio project".
2. Navigate to the `android` directory in this repository and open it.
3. Let Android Studio download any necessary dependencies.
4. Connect your Android device or start an emulator.
5. Click on the "Run" button in Android Studio to build and run the app on your device/emulator.

The app allows you to pick audio or video files from your device and transcribe them to text. The transcriptions can be saved in md/txt/json formats.

## TODO
* [ ] Live transcription demo

Expand Down
29 changes: 29 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 30
defaultConfig {
applicationId "com.usefulsensors.moonshine"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
21 changes: 21 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.usefulsensors.moonshine">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".TranscriptionService" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.usefulsensors.moonshine;

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class AudioUtils {

private static final String TAG = "AudioUtils";

public static File getFileFromUri(Context context, Uri uri) throws IOException {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
File tempFile = File.createTempFile("audio", null, context.getCacheDir());
FileOutputStream outputStream = new FileOutputStream(tempFile);

byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}

outputStream.close();
inputStream.close();

return tempFile;
}

public static String transcribeAudio(File audioFile) {
// Placeholder for actual transcription logic
Log.i(TAG, "Transcribing audio file: " + audioFile.getAbsolutePath());
return "Transcription result";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.usefulsensors.moonshine;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class FilePickerFragment extends Fragment {

private static final int REQUEST_CODE_PICK_AUDIO = 1;
private static final int REQUEST_CODE_PICK_VIDEO = 2;

private TextView statusTextView;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_file_picker, container, false);

statusTextView = view.findViewById(R.id.statusTextView);

Button pickAudioButton = view.findViewById(R.id.pickAudioButton);
pickAudioButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pickAudioFile();
}
});

Button pickVideoButton = view.findViewById(R.id.pickVideoButton);
pickVideoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pickVideoFile();
}
});

return view;
}

private void pickAudioFile() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, REQUEST_CODE_PICK_AUDIO);
}

private void pickVideoFile() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, REQUEST_CODE_PICK_VIDEO);
}

@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == getActivity().RESULT_OK && data != null) {
Uri uri = data.getData();
if (requestCode == REQUEST_CODE_PICK_AUDIO) {
transcribeAudio(uri);
} else if (requestCode == REQUEST_CODE_PICK_VIDEO) {
transcribeVideo(uri);
}
}
}

private void transcribeAudio(Uri uri) {
Intent intent = new Intent(getActivity(), TranscriptionService.class);
intent.setData(uri);
intent.putExtra("type", "audio");
getActivity().startService(intent);
statusTextView.setText("Transcribing audio...");
}

private void transcribeVideo(Uri uri) {
Intent intent = new Intent(getActivity(), TranscriptionService.class);
intent.setData(uri);
intent.putExtra("type", "video");
getActivity().startService(intent);
statusTextView.setText("Transcribing video...");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.usefulsensors.moonshine;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

public class MainActivity extends AppCompatActivity {

private static final int REQUEST_CODE_PICK_AUDIO = 1;
private static final int REQUEST_CODE_PICK_VIDEO = 2;
private static final int REQUEST_CODE_PERMISSIONS = 3;

private TextView statusTextView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

statusTextView = findViewById(R.id.statusTextView);

Button pickAudioButton = findViewById(R.id.pickAudioButton);
pickAudioButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pickAudioFile();
}
});

Button pickVideoButton = findViewById(R.id.pickVideoButton);
pickVideoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pickVideoFile();
}
});

if (!hasPermissions()) {
requestPermissions();
}
}

private boolean hasPermissions() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}

private void requestPermissions() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_PERMISSIONS);
}

private void pickAudioFile() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, REQUEST_CODE_PICK_AUDIO);
}

private void pickVideoFile() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, REQUEST_CODE_PICK_VIDEO);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && data != null) {
Uri uri = data.getData();
if (requestCode == REQUEST_CODE_PICK_AUDIO) {
transcribeAudio(uri);
} else if (requestCode == REQUEST_CODE_PICK_VIDEO) {
transcribeVideo(uri);
}
}
}

private void transcribeAudio(Uri uri) {
Intent intent = new Intent(this, TranscriptionService.class);
intent.setData(uri);
intent.putExtra("type", "audio");
startService(intent);
statusTextView.setText("Transcribing audio...");
}

private void transcribeVideo(Uri uri) {
Intent intent = new Intent(this, TranscriptionService.class);
intent.setData(uri);
intent.putExtra("type", "video");
startService(intent);
statusTextView.setText("Transcribing video...");
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permissions granted
} else {
// Permissions denied
statusTextView.setText("Permissions denied. Cannot access files.");
}
}
}
}
Loading

0 comments on commit 575ed6e

Please sign in to comment.