diff --git a/encode.py b/encode.py index 31eea8b..9dadeb9 100644 --- a/encode.py +++ b/encode.py @@ -1,19 +1,61 @@ from libxheopus import DualOpusEncoder, XopusWriter import wave +import argparse +from tqdm import tqdm -encoder = DualOpusEncoder("restricted_lowdelay", version="hev2") -encoder.set_bitrates(12000) -encoder.set_bitrate_mode("CVBR") -desired_frame_size = encoder.set_frame_size(120) +parser = argparse.ArgumentParser(description='xHE-Opus Encoder') -wav_file = wave.open(r"test.wav", 'rb') +parser.add_argument("input", help='Input wav int16 file path') +parser.add_argument("output", help='Output xopus file path') +parser.add_argument('-sr', "--samprate", help='Set samples rates', default=48000, type=int) +parser.add_argument('-b', '--bitrate', help='Set Bitrate (bps)', default=64000, type=int) +parser.add_argument('-c', '--compress', help='Set compression: 0-10', default=10, type=int) +parser.add_argument('-l', '--loss', help='Set packet loss: 0-100%', default=0, type=int) +parser.add_argument('-fs', '--framesize', help='Set frame size: 120, 100, 80, 60, 40, 20, 10 or 5', default=120, type=int) +parser.add_argument('-bm', '--bitmode', help='Set Bitrate mode: CBR VBR CVBR', default="CVBR") +parser.add_argument('-bw', '--bandwidth', help='Set bandwidth: auto, fullband, superwideband, wideband, mediumband or narrowband', default="fullband") +parser.add_argument('-a', '--app', help='Set bandwidth: restricted_lowdelay, audio, voip', default="restricted_lowdelay") +parser.add_argument('-v', '--ver', help='Set opus version: hev2 (enable 120, 100, 80 framesize), he, exper, stable, old', default="hev2") +parser.add_argument('-pred', '--prediction', help='Enable prediction', action='store_true', default=False) +parser.add_argument('-ph', '--phaseinvert', help='Enable phase invert', action='store_true', default=False) +parser.add_argument('-dtx', help='Enable discontinuous transmission', action='store_true', default=False) +parser.add_argument('-sb', "--samebitrate", help='Enable same bitrate (bitrate x2)', action='store_true', default=False) -file = r"test.xopus" +args = parser.parse_args() + +progress = tqdm() + +progress.desc = "creating encoder..." +encoder = DualOpusEncoder(args.app, args.samprate, args.ver) +encoder.set_bitrates(args.bitrate, args.samebitrate) +encoder.set_bitrate_mode(args.bitmode) +encoder.set_bandwidth(args.bandwidth) +encoder.set_compression(args.compress) +encoder.set_packet_loss(args.loss) +encoder.set_feature(args.prediction, args.phaseinvert, args.dtx) +desired_frame_size = encoder.set_frame_size(args.framesize) + +xopus = XopusWriter(args.output, encoder) + +allframe = 0 + +progress.desc = "reading wav file..." +wav_file = wave.open(args.input, 'rb') + +while True: + frames = wav_file.readframes(desired_frame_size) + + if not frames: + break # Break the loop when all frames have been read + + allframe += len(frames) + +progress.total = allframe +wav_file.rewind() -xopus = XopusWriter(file, encoder) # Read and process the WAV file in chunks -print("encoding...") +progress.desc = "encoding..." while True: frames = wav_file.readframes(desired_frame_size) @@ -22,5 +64,7 @@ while True: xopus.write(frames) + progress.update(len(frames)) + xopus.close() -print("encoded") \ No newline at end of file +progress.desc = "encoded" diff --git a/libxheopus.py b/libxheopus.py index 6bf577e..42a86f8 100644 --- a/libxheopus.py +++ b/libxheopus.py @@ -1,7 +1,6 @@ import importlib import math import struct - import pyogg import os import numpy as np @@ -109,7 +108,6 @@ class DualOpusEncoder: @return chunk size """ - if self.version != "hev2" and size > 60: raise ValueError("non hev2 can't use framesize > 60") @@ -336,12 +334,18 @@ class XopusReader: if data.startswith(b"\\xeof\\xeof"): break else: - yield decoder.decode(data) + try: + yield decoder.decode(data) + except: + yield b"" else: decodedlist = [] for data in self.xopusline[1:]: if data.startswith(b"\\xeof\\xeof"): break else: - decodedlist.append(decoder.decode(data)) + try: + decodedlist.append(decoder.decode(data)) + except: + decodedlist.append(b"") return decodedlist \ No newline at end of file diff --git a/player.py b/player.py index 5d5980e..a7f5ecf 100644 --- a/player.py +++ b/player.py @@ -1,25 +1,60 @@ import pyaudio from libxheopus import DualOpusDecoder, XopusReader +import argparse +from tqdm import tqdm +import wave + +parser = argparse.ArgumentParser(description='xHE-Opus Decoder/Player') + +parser.add_argument("input", help='Input xopus file path') +parser.add_argument("-o", "--output", help='Output wav int16 file path') + +args = parser.parse_args() + +progress = tqdm() # Initialize PyAudio p = pyaudio.PyAudio() decoder = DualOpusDecoder() -streamoutput = p.open(format=pyaudio.paInt16, channels=2, rate=48000, output=True) +xopusdecoder = XopusReader(args.input) -xopusdecoder = XopusReader(r"test.xopus") +metadata = xopusdecoder.readmetadata() -print(xopusdecoder.readmetadata()) +progress.total = metadata["footer"]["length"] +print("\nloudness:", metadata["footer"]["contentloudness"], "DBFS") -try: +print(metadata["header"]) + +if not args.output: + progress.desc = "playing..." + streamoutput = p.open(format=pyaudio.paInt16, channels=2, rate=48000, output=True) + try: + for data in xopusdecoder.decode(decoder, True): + streamoutput.write(data) + + progress.update(1) + + except KeyboardInterrupt: + print("Interrupted by user") + finally: + # Clean up PyAudio streams and terminate PyAudio + streamoutput.stop_stream() + streamoutput.close() + p.terminate() + + progress.desc = "played" +else: + progress.desc = "converting..." + outwav = wave.open("output.wav", "w") + # Set the parameters of the WAV file + outwav.setnchannels(2) # Mono + outwav.setsampwidth(2) # 2 bytes (16 bits) per sample + outwav.setframerate(48000) for data in xopusdecoder.decode(decoder, True): - streamoutput.write(data) + # Write the audio data to the file + outwav.writeframes(data) + progress.update(1) -except KeyboardInterrupt: - print("Interrupted by user") -finally: - # Clean up PyAudio streams and terminate PyAudio - streamoutput.stop_stream() - streamoutput.close() - p.terminate() \ No newline at end of file + progress.desc = "converted" \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..cb398f9 --- /dev/null +++ b/readme.md @@ -0,0 +1,38 @@ +# xHE-Opus +xHE-Opus is extended High Efficiency. It use Dual-Encoder to encode per channel and bitrate is divide 2 for per channel. + +# Install +[PyOgg (damp11113 moded)](https://github.com/damp11113/PyOgg) + +# Using +## Encoder +to encode you can use +```bash +$ python3 encode.py +``` +```bash +usage: encode.py [-h] [-sr SAMPRATE] [-b BITRATE] [-c COMPRESS] [-l LOSS] [-fs FRAMESIZE] [-bm BITMODE] + [-bw BANDWIDTH] [-a APP] [-v VER] [-pred] [-ph] [-dtx] [-sb] + input output +encode.py: error: the following arguments are required: input, output +``` +simple example +```bash +$ python3 encode.py input.wav output.xopus +``` +This will convert to xhe-opus with bitrate 64Kbps (32Kbps per channel), bitrate mode is CVBR, compression is 10 and app is hev2 + +or if you want to set bitrate you can use `-b ` input bit per sec (bps) like +```bash +$ python3 encode.py input.wav output.xopus -b 16000 +``` + +## Decoder/Player +To player or decode this file you can use +```bash +$ python3 input.xopus +``` +or if you want only convert to wav you can use +```bash +$ python3 input.xopus -o output.wav +``` \ No newline at end of file diff --git a/test.wav b/test.wav index 7ad6afb..1c58ae1 100644 Binary files a/test.wav and b/test.wav differ diff --git a/test.xopus b/test.xopus index 091be43..c7ee663 100644 Binary files a/test.xopus and b/test.xopus differ diff --git a/test2.xopus b/test2.xopus new file mode 100644 index 0000000..9935c18 Binary files /dev/null and b/test2.xopus differ