Skip to content

Commit

Permalink
Merge pull request #30 from skycatminepokie/development
Browse files Browse the repository at this point in the history
v0.5.0
  • Loading branch information
skycatminepokie authored Oct 2, 2024
2 parents d2700b5 + e92148a commit 66b80a2
Show file tree
Hide file tree
Showing 29 changed files with 487 additions and 260 deletions.
8 changes: 3 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
- Added keybind to make a new clip
- Added export options
- Better notifications for exporting errors
- Added clips for receiving messages from players and from the server (not enabled by default)
- Added inverse clips - basically "don't save this" instead of "save this"
- Better progress info
- Option to not overwrite on conflict
- Added an export grouping mode - export clips to one file or to multiple files
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ org.gradle.parallel=true
mod_id=autocut
mod_group=com.skycatdev.autocut
mod_name=Autocut
mod_version=0.4.0
mod_version=0.5.0
# The Minecraft version that is supported, for mod meta
earliest_minecraft=[VERSIONED]
latest_minecraft=[VERSIONED]
Expand Down
1 change: 1 addition & 0 deletions src/client/java/com/skycatdev/autocut/AutocutClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.skycatdev.autocut;

import com.skycatdev.autocut.clips.ClipTypes;
import com.skycatdev.autocut.record.RecordingManager;
import io.obswebsocket.community.client.OBSRemoteController;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.skycatdev.autocut.export.ExportHelper;
import com.skycatdev.autocut.record.OBSHandler;
import com.skycatdev.autocut.record.RecordingManager;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.text.Text;
Expand Down
24 changes: 24 additions & 0 deletions src/client/java/com/skycatdev/autocut/Utils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.skycatdev.autocut;

import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
Expand All @@ -14,6 +16,7 @@
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;

public class Utils {
/**
Expand Down Expand Up @@ -78,4 +81,25 @@ public static <T> void saveToJson(File file, Codec<T> codec, T instance) throws
Streams.write(serialized, jsonWriter);
}
}

public static String rangeToFFmpegRange(Range<Long> range, long startTime) {
long rangeStart = range.lowerBoundType().equals(BoundType.OPEN) ? range.lowerEndpoint() + 1 : range.lowerEndpoint();
long rangeEnd = range.upperBoundType().equals(BoundType.OPEN) ? range.upperEndpoint() - 1 : range.upperEndpoint();
return String.format("%dms:%dms", rangeStart - startTime, rangeEnd - startTime);
}

/**
* Calculates the total space covered by a set of non-overlapping ranges
* @param ranges A collection of non-overlapping ranges
* @return The total space covered by a set of non-overlapping ranges
*/
public static long totalSpace(Collection<Range<Long>> ranges) {
long totalSpace = 0;
for (Range<Long> range : ranges) {
long rangeStart = range.lowerBoundType().equals(BoundType.OPEN) ? range.lowerEndpoint() + 1 : range.lowerEndpoint();
long rangeEnd = range.upperBoundType().equals(BoundType.OPEN) ? range.upperEndpoint() - 1 : range.upperEndpoint();
totalSpace += rangeEnd - rangeStart;
}
return totalSpace;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.skycatdev.autocut.Autocut;
import com.skycatdev.autocut.config.ExportGroupingMode;
import dev.isxander.yacl3.api.OptionDescription;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
Expand All @@ -15,12 +16,12 @@ public class AttackEntityClipType extends ClipType {
public static final Identifier ID = Identifier.of(Autocut.MOD_ID, "attack_entity");
public static final Codec<AttackEntityClipType> CODEC = RecordCodecBuilder.create(instance -> ClipTypes.addDefaultConfigFields(instance).apply(instance, AttackEntityClipType::new));

public AttackEntityClipType(boolean active, boolean shouldRecord, long startOffset, long endOffset, boolean inverse) {
super(ID, active, shouldRecord, startOffset, endOffset, inverse, true, true, 100, 100, false);
public AttackEntityClipType(boolean active, boolean shouldRecord, long startOffset, long endOffset, boolean inverse, ExportGroupingMode exportGroupingMode) {
super(ID, active, shouldRecord, startOffset, endOffset, inverse, exportGroupingMode, true, true, 100, 100, false, ExportGroupingMode.NONE);
}

public AttackEntityClipType() {
super(ID, true, true, 100, 100, false, true, true, 100, 100, false);
this(true, true, 100, 100, false, ExportGroupingMode.NONE);
}

@Override
Expand All @@ -34,6 +35,6 @@ public Text getOptionGroupName() {
}

public Clip createClip(long time, PlayerEntity player, Entity entity) {
return new Clip(time - getStartOffset(), time, time + getEndOffset(), ID, isActive(), isInverse(),"Attacked " + entity.getNameForScoreboard(), player.getNameForScoreboard(), entity.getType().getName().getString(), player.getPos(), entity.getPos());
return new Clip(time - getStartOffset(), time, time + getEndOffset(), ID, isActive(), isInverse(), getExportGroupingMode(), "Attacked " + entity.getNameForScoreboard(), player.getNameForScoreboard(), entity.getType().getName().getString(), player.getPos(), entity.getPos());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.skycatdev.autocut.Autocut;
import com.skycatdev.autocut.config.ExportGroupingMode;
import dev.isxander.yacl3.api.OptionDescription;
import net.minecraft.block.BlockState;
import net.minecraft.client.network.ClientPlayerEntity;
Expand All @@ -18,11 +19,11 @@
public class BreakBlockClipType extends ClipType {
public static final Identifier ID = Identifier.of(Autocut.MOD_ID, "break_block");
public static final Codec<BreakBlockClipType> CODEC = RecordCodecBuilder.create(instance -> ClipTypes.addDefaultConfigFields(instance).apply(instance, BreakBlockClipType::new));
public BreakBlockClipType(boolean active, boolean shouldRecord, long startOffset, long endOffset, boolean inverse) {
super(ID, active, shouldRecord, startOffset, endOffset, inverse, true, true, 100, 100, false);
public BreakBlockClipType(boolean active, boolean shouldRecord, long startOffset, long endOffset, boolean inverse, ExportGroupingMode exportGroupingMode) {
super(ID, active, shouldRecord, startOffset, endOffset, inverse, exportGroupingMode, true, true, 100, 100, false, exportGroupingMode);
}
public BreakBlockClipType() {
super(ID, true, true, 100, 100, false, true, true, 100, 100, false);
this(true, true, 100, 100, false, ExportGroupingMode.NONE);
}

@Override
Expand All @@ -36,6 +37,6 @@ public Text getOptionGroupName() {
}

public Clip createClip(long time, ClientPlayerEntity player, BlockPos pos, BlockState state) {
return new Clip(time - getStartOffset(), time, time + getEndOffset(), ID, isActive(), isInverse(), "Broke " + state.getBlock().getName().getString(), player.getNameForScoreboard(), Registries.BLOCK.getId(state.getBlock()).toString(), player.getPos(), Vec3d.of(pos));
return new Clip(time - getStartOffset(), time, time + getEndOffset(), ID, isActive(), isInverse(), getExportGroupingMode(), "Broke " + state.getBlock().getName().getString(), player.getNameForScoreboard(), Registries.BLOCK.getId(state.getBlock()).toString(), player.getPos(), Vec3d.of(pos));
}
}
29 changes: 23 additions & 6 deletions src/client/java/com/skycatdev/autocut/clips/Clip.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package com.skycatdev.autocut.clips;

import com.google.common.collect.Range;
import com.google.common.collect.TreeRangeSet;
import com.skycatdev.autocut.config.ExportGroupingMode;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Objects;

/**
* A period of time in a recording. Timestamps are UNIX time, not relative to the recording.
* A period of time in a record. Timestamps are UNIX time, not relative to the record.
*/
public record Clip(long in, long time, long out, @NotNull Identifier type, boolean active, boolean inverse, @Nullable String description,
public record Clip(long in, long time, long out, @NotNull Identifier type, boolean active, boolean inverse,
@NotNull ExportGroupingMode exportGroupingMode, @Nullable String description,
@Nullable String source, @Nullable String object, @Nullable Vec3d sourceLocation,
@Nullable Vec3d objectLocation) {
public Clip {
Expand All @@ -23,7 +25,7 @@ public record Clip(long in, long time, long out, @NotNull Identifier type, boole
* @return A deep copy of this clip
*/
public Clip copy() { // Deep copy, though since everything inside is immutable that doesn't mean much.
return new Clip(in, time, out, type, active, inverse, description, source, object, sourceLocation, objectLocation);
return new Clip(in, time, out, type, active, inverse, exportGroupingMode, description, source, object, sourceLocation, objectLocation);
}

/**
Expand All @@ -37,7 +39,7 @@ public long duration() {
* Converts the clip to a {@code between} statement for ffmpeg.
*
* @param variable The variable for time in seconds.
* @param recordingStartTime The time the relevant recording started, used for offsetting the clip time
* @param recordingStartTime The time the relevant record started, used for offsetting the clip time
* @return a {@code between} statement for ffmpeg statements.
*/
public String toBetweenStatement(String variable, long recordingStartTime) {
Expand All @@ -50,10 +52,25 @@ public Range<Long> toRange() {
return Range.closed(in, out);
}

public static TreeRangeSet<Long> toRange(Collection<Clip> clips) {
TreeRangeSet<Long> range = TreeRangeSet.create();
for (Clip clip : clips) {
if (!clip.inverse()) {
range.add(clip.toRange());
}
}
for (Clip clip : clips) {
if (clip.inverse()) {
range.remove(clip.toRange());
}
}
return range;
}

/**
* Converts the clip to a range for FFmpeg's trim filter.
*
* @param startTime The time the recording started at
* @param startTime The time the record started at
* @return A range for FFmpeg's trim filter
*/
public String toTrimRange(long startTime) {
Expand Down
16 changes: 13 additions & 3 deletions src/client/java/com/skycatdev/autocut/clips/ClipBuilder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.skycatdev.autocut.clips;

import com.skycatdev.autocut.config.ExportGroupingMode;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import org.jetbrains.annotations.NotNull;
Expand All @@ -12,23 +13,32 @@ public class ClipBuilder {
private @NotNull Identifier type;
private boolean active = true;
private boolean inverse;
private @NotNull ExportGroupingMode exportGroupingMode;
private @Nullable String description;
private @Nullable String source;
private @Nullable String object;
private @Nullable Vec3d sourceLocation;
private @Nullable Vec3d objectLocation;

public ClipBuilder(long in, long time, long out, @NotNull Identifier type, boolean active, boolean inverse) {
public ClipBuilder(long in, long time, long out, @NotNull Identifier type, boolean active, boolean inverse, ExportGroupingMode exportGroupingMode) {
this.in = in;
this.time = time;
this.out = out;
this.type = type;
this.active = active;
this.inverse = inverse;
this.exportGroupingMode = exportGroupingMode;
}

public Clip build() {
return new Clip(in, time, out, type, active, inverse, description, source, object, sourceLocation, objectLocation);
return new Clip(in, time, out, type, active, inverse, exportGroupingMode, description, source, object, sourceLocation, objectLocation);
}

public @NotNull ExportGroupingMode getExportGroupingMode() {
return exportGroupingMode;
}

public void setExportGroupingMode(@NotNull ExportGroupingMode exportGroupingMode) {
this.exportGroupingMode = exportGroupingMode;
}

public ClipBuilder setActive(boolean active) {
Expand Down
39 changes: 36 additions & 3 deletions src/client/java/com/skycatdev/autocut/clips/ClipType.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.skycatdev.autocut.clips;

import com.skycatdev.autocut.config.ExportGroupingMode;
import com.skycatdev.autocut.datagen.AutocutEnglishLangProvider;
import dev.isxander.yacl3.api.Option;
import dev.isxander.yacl3.api.OptionDescription;
import dev.isxander.yacl3.api.OptionGroup;
import dev.isxander.yacl3.api.controller.EnumControllerBuilder;
import dev.isxander.yacl3.api.controller.LongFieldControllerBuilder;
import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder;
import net.minecraft.text.Text;
Expand All @@ -13,7 +15,7 @@
* A type of {@link Clip} to be recorded.
* Every implementation of this should have a way to create a clip based on related data.
*/
public abstract class ClipType {
public abstract class ClipType { // TODO: Make a method that does the stuff it can on a ClipBuilder.
/**
* The {@link Identifier} stored with clips of this type in the database.
*/
Expand All @@ -38,6 +40,12 @@ public abstract class ClipType {
* If the clip should be inverse by default.
*/
private final boolean inverseDefault;
/**
* What group a clip should be exported with by default
*
* @see ExportGroupingMode
*/
private final ExportGroupingMode exportGroupingModeDefault;
/**
* Whether new clips of this type should be marked as active.
*/
Expand All @@ -58,19 +66,29 @@ public abstract class ClipType {
* If the clips of this type should be an inverted clip ("discard this footage")
*/
private boolean inverse;
/**
* What group a clip should be exported with
*
* @see ExportGroupingMode
*/
private ExportGroupingMode exportGroupingMode;

public ClipType(Identifier id, boolean active, boolean shouldRecord, long startOffset, long endOffset, boolean inverse, boolean activeDefault, boolean shouldRecordDefault, long startOffsetDefault, long endOffsetDefault, boolean inverseDefault) {
public ClipType(Identifier id, boolean active, boolean shouldRecord, long startOffset, long endOffset, boolean inverse, ExportGroupingMode exportGroupingMode, boolean activeDefault, boolean shouldRecordDefault, long startOffsetDefault, long endOffsetDefault, boolean inverseDefault, ExportGroupingMode exportGroupingModeDefault) {
this.id = id;

this.active = active;
this.shouldRecord = shouldRecord;
this.startOffset = startOffset;
this.endOffset = endOffset;
this.inverse = inverse;
this.exportGroupingMode = exportGroupingMode;

this.activeDefault = activeDefault;
this.shouldRecordDefault = shouldRecordDefault;
this.startOffsetDefault = startOffsetDefault;
this.endOffsetDefault = endOffsetDefault;
this.inverseDefault = inverseDefault;
this.exportGroupingModeDefault = exportGroupingModeDefault;
}

/**
Expand Down Expand Up @@ -112,13 +130,20 @@ public void addOptions(OptionGroup.Builder builder) {
.controller(LongFieldControllerBuilder::create)
.build()
);
builder.option(Option.<Boolean>createBuilder() // TODO: Localization
builder.option(Option.<Boolean>createBuilder() // TODO: Localize
.name(Text.of("Invert new clips"))
.description(OptionDescription.of(Text.of("If new clips should be the opposite of clips - the footage they cover will NOT be exported.")))
.binding(inverseDefault, this::isInverse, this::setInverse)
.controller(TickBoxControllerBuilder::create)
.build()
);
builder.option(Option.<ExportGroupingMode>createBuilder() // TODO: Localize
.name(Text.of("Grouping mode"))
.description(OptionDescription.of(Text.of("What clips this group should be exported with.")))
.binding(exportGroupingModeDefault, this::getExportGroupingMode, this::setExportGroupingMode)
.controller((option) -> EnumControllerBuilder.create(option).enumClass(ExportGroupingMode.class).formatValue(ExportGroupingMode::getName))
.build()
);
}

/**
Expand All @@ -139,6 +164,14 @@ public void setEndOffset(long endOffset) {
this.endOffset = endOffset;
}

public ExportGroupingMode getExportGroupingMode() {
return exportGroupingMode;
}

public void setExportGroupingMode(ExportGroupingMode exportGroupingMode) {
this.exportGroupingMode = exportGroupingMode;
}

public Identifier getId() {
return id;
}
Expand Down
10 changes: 8 additions & 2 deletions src/client/java/com/skycatdev/autocut/clips/ClipTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.mojang.datafixers.Products;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.Lifecycle;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.skycatdev.autocut.Autocut;
import com.skycatdev.autocut.config.ConfigHandler;
import com.skycatdev.autocut.config.ExportGroupingMode;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.SimpleRegistry;
Expand Down Expand Up @@ -44,12 +46,16 @@ public class ClipTypes {
* @return The thing that you {@code .and} or {@code .apply} on.
* @see net.minecraft.loot.entry.LeafEntry#addLeafFields(RecordCodecBuilder.Instance)
*/
public static <T extends ClipType> Products.P5<RecordCodecBuilder.Mu<T>, Boolean, Boolean, Long, Long, Boolean> addDefaultConfigFields(RecordCodecBuilder.Instance<T> instance) {
public static <T extends ClipType> Products.P6<RecordCodecBuilder.Mu<T>, Boolean, Boolean, Long, Long, Boolean, ExportGroupingMode> addDefaultConfigFields(RecordCodecBuilder.Instance<T> instance) {
return instance.group(Codec.BOOL.fieldOf("should_record").forGetter(ClipType::shouldRecord),
Codec.BOOL.fieldOf("active").forGetter(ClipType::isActive),
Codec.LONG.fieldOf("start_offset").forGetter(ClipType::getStartOffset),
Codec.LONG.fieldOf("end_offset").forGetter(ClipType::getEndOffset),
Codec.BOOL.fieldOf("inverse").orElse(false).forGetter(ClipType::isInverse));
Codec.BOOL.fieldOf("inverse").orElse(false).forGetter(ClipType::isInverse),
Identifier.CODEC.comapFlatMap((id) -> {
ExportGroupingMode groupingMode = ExportGroupingMode.fromId(id);
return groupingMode != null ? DataResult.success(groupingMode) : DataResult.error(() -> "Not a valid ExportGroupingMode: " + id);
}, ExportGroupingMode::getId).fieldOf("grouping_mode").orElse(ExportGroupingMode.NONE).forGetter(ClipType::getExportGroupingMode));
}

/**
Expand Down
Loading

0 comments on commit 66b80a2

Please sign in to comment.