Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b328a49
Remington's UI|Initial Commit
Chaospyke Mar 10, 2026
c82e0ca
Created MetadataSearchTests
Mar 10, 2026
1bb8d88
Moved location of CreateTestXmlFile
Mar 10, 2026
ebc7a44
Added Unit Tests
Chaospyke Mar 10, 2026
41737d2
Added SearchXMLFile method
OwenRidings Mar 10, 2026
0641ffe
removed unnecessary function from Listpanel.cs
Chaospyke Mar 11, 2026
e353727
Added Functionality for delay if search text is one or two chars. If …
Chaospyke Mar 12, 2026
f2b612b
Created SearchSession method
OwenRidings Mar 12, 2026
46bd201
Merge pull request #1 from umbrella-machine/searching-merge
umbrella-machine Mar 12, 2026
8c80221
Attempted to connect search to UI
Mar 12, 2026
19bc1a8
SearchSession gets called from search bar in UI
Mar 12, 2026
1328a94
Fixed partial matching
Mar 13, 2026
f4e9c09
Added searching functionality
OwenRidings Mar 13, 2026
4e2e2b6
Merge pull request #2 from umbrella-machine/remi_owen_merge
umbrella-machine Mar 13, 2026
c0826c3
Fixed misnamed function
Chaospyke Mar 13, 2026
c6d7356
Fixed SearchSessions method :)
OwenRidings Mar 13, 2026
1ac3cb0
Merge pull request #3 from umbrella-machine/search-session-data
umbrella-machine Mar 13, 2026
8d9a9e8
I did the thing chat
Mar 13, 2026
2885869
Merge pull request #4 from umbrella-machine/working-filter
umbrella-machine Mar 13, 2026
abdce53
Added comments
Mar 13, 2026
6af5c4e
Merge branch 'main' into working-filter
umbrella-machine Mar 13, 2026
c3f60c6
Merge pull request #5 from umbrella-machine/working-filter
umbrella-machine Mar 13, 2026
2fddd0d
Correct SearchSessions method
Mar 13, 2026
f602e00
Removed tests for a now nonexistent method
Mar 13, 2026
28fde11
Small edits
OwenRidings Mar 13, 2026
a17c40e
tried adding getCustomFieldIds
OwenRidings Mar 13, 2026
5d0e401
adding getCustomFieldsId to search
Chaospyke Mar 13, 2026
52ac8c0
added delay to 3 or more char and cleaned up debug code
Chaospyke Mar 13, 2026
a61e67d
custom fields working for session
OwenRidings Mar 13, 2026
366eda3
Added search support for custom fields
Mar 13, 2026
2bf3f69
Added searching for contributer notes
Mar 13, 2026
8112e43
Added some commenting
Mar 13, 2026
31c3f7e
Merge branch 'main' into custom-fields-added
umbrella-machine Mar 13, 2026
2cb965c
Merge pull request #6 from umbrella-machine/custom-fields-added
umbrella-machine Mar 13, 2026
15fdf78
Update release notes for version 3.7.5
umbrella-machine Mar 13, 2026
1bec254
Readded found flag
OwenRidings Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion DistFiles/releaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
* SayMore is now a 64-bit application, so it will make better use of available memory and avoid out-of-memory errors.
* SayMore now targets .Net Framework 4.8, which is the latest version supported on Windows 7 through Windows 11. It will not run on Windows versions earlier than 7.

## 3.7.5 (15 March 2026)
* Added session metadata searching for most data files except audio file custom fields and audio annotations.
* Added search bar feature to the UI for the sessions tab

## 3.7.4 (30 July 2025)
* Small improvement in the logic to guess at the correct initial writing system for translations when exporting FLEx Interlinear files (flextext).

Expand Down Expand Up @@ -238,4 +242,4 @@ SayMore doesn't provide any help with Informed Consent/permission situations whe

There is no way yet to customize the file naming conventions.

When using the "Add Files..." button, you can only choose one file at a time.
When using the "Add Files..." button, you can only choose one file at a time.
41 changes: 20 additions & 21 deletions SampleData/EdoloSample/EdoloSample.sprj
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Iso639Code />
<transcriptionFont />
<freeTranslationFont />
<AutoSegmentersettings minSegmentLength="0" maxSegmentLength="0" preferrerdPauseLength="0" optimumLengthClampingFactor="0" />
<Title>Edolo Sample</Title>
<FundingProjectTitle />
<ProjectDescription>The Etoro, or Edolo, are a tribe and ethnic group of the Southern Highlands Province of Papua New Guinea. Their territory comprises the southern slopes of Mt. Sisa, along the southern edge of the central mountain range of New Guinea, near the Papuan Plateau.</ProjectDescription>
<VernacularISO3CodeAndName>etr: Edolo</VernacularISO3CodeAndName>
<Location />
<Region>Southern Highlands Province</Region>
<Country>Papua New Guinea</Country>
<Continent>Oceania</Continent>
<ContactPerson />
<AccessProtocol>None</AccessProtocol>
<ContentType />
<Applications />
<DateAvailable />
<RightsHolder />
<Depositor />
<RelatedPublications />
<transcriptionFont />
<freeTranslationFont />
<workingLanguageFont />
<AutoSegmenterSettings minSegmentLength="0" maxSegmentLength="0" preferredPauseLength="0" optimumLengthClampingFactor="0" />
<Title>Edolo Sample</Title>
<FundingProjectTitle />
<ProjectDescription>The Etoro, or Edolo, are a tribe and ethnic group of the Southern Highlands Province of Papua New Guinea. Their territory comprises the southern slopes of Mt. Sisa, along the southern edge of the central mountain range of New Guinea, near the Papuan Plateau.</ProjectDescription>
<VernacularISO3CodeAndName>etr: Edolo</VernacularISO3CodeAndName>
<AnalysisISO3CodeAndName>eng: English</AnalysisISO3CodeAndName>
<Location />
<Region>Southern Highlands Province</Region>
<Country>Papua New Guinea</Country>
<Continent>Oceania</Continent>
<ContactPerson />
<AccessProtocol>None</AccessProtocol>
<DateAvailable />
<RightsHolder />
<Depositor />
<IMDIOutputDirectory />
</Project>
5 changes: 3 additions & 2 deletions SampleData/EdoloSample/People/Awi Heole/Awi Heole.person
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Person>
<primaryLanguage type="string">etr:Edolo</primaryLanguage>
<primaryLanguageLearnedIn type="string">Huya</primaryLanguageLearnedIn>
Expand All @@ -10,7 +10,8 @@
<gender type="string">Male</gender>
<education type="string">Grade 2</education>
<primaryOccupation type="string">Subsistence Farmer</primaryOccupation>
<privacyProtection type="string">false</privacyProtection>
<CustomFields>
<Current_Village type="string">Huya</Current_Village>
<Current_Village type="string">Huya</Current_Village>
</CustomFields>
</Person>
35 changes: 32 additions & 3 deletions SampleData/EdoloSample/Sessions/ETR009/ETR009.session
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Session>
<?xml version="1.0" encoding="utf-8"?>
<Session version="2.0">
<title type="string">The story behind how we catch fish with poison bark</title>
<participants type="string">Awi Heole; Ilawi Amosa</participants>
<participants type="string">Awi Heole; Hatton; Ilawi Amosa</participants>
<genre type="string">narrative</genre>
<access type="string">Open</access>
<location type="string">Huya</location>
Expand All @@ -10,4 +10,33 @@
<date type="string">2010-06-06</date>
<notes type="string">hello</notes>
<status type="string">Incoming</status>
<CustomFields>
<EarthTYpe type="string">Magma</EarthTYpe>
</CustomFields>
<contributions type="xml">
<contributor>
<name>Awi Heole</name>
<role>participant</role>
<date>0001-01-01</date>
<notes></notes>
</contributor>
<contributor>
<name>Ilawi Amosa</name>
<role>participant</role>
<date>0001-01-01</date>
<notes></notes>
</contributor>
<contributor>
<name>Hatton</name>
<role>recorder</role>
<date>2010-09-10</date>
<notes></notes>
</contributor>
<contributor>
<name>Awi Heole</name>
<role>speaker</role>
<date>2010-09-10</date>
<notes></notes>
</contributor>
</contributions>
</Session>
29 changes: 16 additions & 13 deletions SampleData/EdoloSample/Sessions/ETR009/ETR009_Careful.mp3.meta
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<MetaData>
<Device type="string">Shure X2u A/D, into GoldWave</Device>
<Microphone type="string">Sure SM10A Headset</Microphone>
<Channels type="string">mono</Channels>
<notes type="string">This was downgraded to low bit-rate mp3 to keep the SayMore installer from getting huge.</notes>
<contributions type="xml">
<contributor>
<name>Hatton</name>
<role>recorder</role>
<date>2010-09-10</date>
<notes></notes>
</contributor>
<contributor>
<name>Awi Heole</name>
<role>speaker</role>
<date>2010-09-10</date>
<notes></notes>
</contributor>
<contributor>
<name>Hatton</name>
<role>recorder</role>
<date>2010-09-10</date>
<notes></notes>
</contributor>
<contributor>
<name>Awi Heole</name>
<role>speaker</role>
<date>2010-09-10</date>
<notes></notes>
</contributor>
</contributions>
<Duration type="string">00:00:25</Duration>
<Sample_Rate type="string">44100 Hz</Sample_Rate>
<Audio_Bit_Rate type="string">96 kbps</Audio_Bit_Rate>
<CustomFields>
<SoundType type="string">Brtick</SoundType>
</CustomFields>
</MetaData>
26 changes: 14 additions & 12 deletions SampleData/EdoloSample/Sessions/ETR009/ETR009_Tiny.mp4.meta
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<MetaData>
<Device type="string">Zoom Q3</Device>
<Microphone type="string">Built-in</Microphone>
<fps type="string">30</fps>
<notes type="string">This was downgraded to small picture and low bit-rate mp3 to keep the SayMore installer from getting huge.</notes>
<contributions type="xml">
<contributor>
<name>Hatton</name>
<role>recorder</role>
<date>2010-09-10</date>
<notes></notes>
</contributor>
<contributor>
<name>Hatton</name>
<role>recorder</role>
<date>2010-09-10</date>
<notes></notes>
</contributor>
</contributions>
<Duration type="string">00:00:10</Duration>
<Channels type="string">stereo</Channels>
<Sample_Rate type="string">48000 Hz</Sample_Rate>
<Audio_Bit_Rate type="string">96 kbps</Audio_Bit_Rate>
<Video_Bit_Rate type="string">218 kbps</Video_Bit_Rate>
<Resolution type="string">176 x 144</Resolution>
<Frame_Rate type="string">30 frames/second</Frame_Rate>
<CustomFields>
<fps type="string">30</fps>
<Duration type="string">00:00:10</Duration>
<Channels type="string">stereo</Channels>
<Sample_Rate type="string">48000 Hz</Sample_Rate>
<Audio_Bit_Rate type="string">96 kbps</Audio_Bit_Rate>
</CustomFields>
</MetaData>
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<MetaData />
166 changes: 166 additions & 0 deletions src/SayMore/Model/MetadataSearch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using SayMore.Model.Files;
using SIL.Core.ClearShare;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace SayMore.Model
{
/// ----------------------------------------------------------------------------------------
/// <summary>
/// The class that contains the data searching methods.
/// </summary>
/// ----------------------------------------------------------------------------------------
public class MetadataSearch
{

private readonly ProjectContext _projectContext;

public MetadataSearch(ProjectContext projectContext)
{
_projectContext = projectContext;
}

// Searchable tags based on file type.
private static readonly HashSet<string> sessionFileSearchableTags = new HashSet<string>
{
"genre",
"title",
"setting",
"participants",
"situation",
"synopsis",
"location",
"access",
"notes",
"continent",
"country",
"region",
"address",
"sub-genre",
"name",
"contributors",
"additional_location_country",
"additional_location_continent",
"additional_location_region",
"additional_location_address",
"additional_sub-genre",
"additional_interactivity",
"additional_planning_type",
"additional_involvement",
"additional_social_context",
"additional_task"
};

// We know this is not how you search for annotation files.
private static readonly HashSet<string> annotationFileSearchableTags = new HashSet<string>
{
"annotation_value"
};

private static readonly HashSet<string> otherFileSearchableTags = new HashSet<string>
{
"notes",
"name",
"microphone",
"device",
"participants",
"annotation_value",
"recordist",
"speaker"
};

public IEnumerable<string> SearchSessions(string query)
{
var allSessions = _projectContext.Project.GetAllSessions(CancellationToken.None);

foreach (var session in allSessions)
{
bool found = false;
foreach (var componentFile in session.GetComponentFiles())
{
// Determins what file type is being searched for to determine searchable tags.
HashSet<string> searchableTags;
if (componentFile.FileType is SessionFileType)
{
searchableTags = new HashSet<string>(sessionFileSearchableTags.Union(GetCustomFieldIds(componentFile)));
}
else if (componentFile is AnnotationComponentFile)
{
searchableTags = annotationFileSearchableTags;
}
else if (componentFile is OralAnnotationComponentFile)
{
searchableTags = annotationFileSearchableTags;
}
else
{
searchableTags = new HashSet<string>(otherFileSearchableTags.Union(GetCustomFieldIds(componentFile)));
}

// Actually peforms the search and yields the session ID if found.
var fields = componentFile.MetaDataFieldValues;

foreach (var field in fields)
{
if (searchableTags.Contains(field.FieldId?.ToLowerInvariant()) &&
(field.Value?.ToString() ?? string.Empty).IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)
{
found = true;
yield return session.Id;
break;
}
}

// If the query wasn't found in the offical fields or the custom fields, check the contributer notes.
if (!found && componentFile.FileType is SessionFileType && ContainsContributorNotes(componentFile, query))
{
found = true;
yield return session.Id;
}
}
if (found) break;
}
}


// Returns the custom fields for a given component file.
// Note: this does not work for audio files.
private HashSet<string> GetCustomFieldIds(ComponentFile file)
{
HashSet<string> customFields = new HashSet<string>();

if (file is ProjectElementComponentFile projectElementFile)
{
customFields = new HashSet<string>(
projectElementFile.GetCustomFields()
.Select(f => f.FieldId?.ToLowerInvariant())
.Where(id => !string.IsNullOrEmpty(id))
);
}

return customFields;
}

// Checks through contributer notes of a given component file for a query.
private static bool ContainsContributorNotes(ComponentFile file, string query)
{
var contributionsField = file.MetaDataFieldValues
.FirstOrDefault(f => f.FieldId == SessionFileType.kContributionsFieldName);

if (contributionsField?.Value is ContributionCollection contributions)
{
foreach (var contribution in contributions)
{
if (!string.IsNullOrEmpty(contribution.Comments) &&
contribution.Comments.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}
}
return false;
}
}
}
2 changes: 1 addition & 1 deletion src/SayMore/Model/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ public void DisplayInitialArchiveSummary(
throw new OperationCanceledException();
var element = (kvp.Key.StartsWith("\n") || kvp.Key.Length > 0 ?
LocalizationManager.GetString("DialogBoxes.ArchivingDlg.ContributorElementName", "Contributor") :
LocalizationManager.GetString("DialogBoxes.ArchivingDlg.SessionElementName", "Sessions"));
LocalizationManager.GetString("DialogBoxes.ArchivingDlg.SessionElementName", "Sessionsg"));

model.DisplayMessage(string.Format(fmt, element, (kvp.Key.StartsWith("\n") || kvp.Key.Length > 0 ? kvp.Key.Substring(1) : Title)),
ArchivingDlgViewModel.MessageType.Progress);
Expand Down
1 change: 1 addition & 0 deletions src/SayMore/ProjectContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ protected void BuildSubContainerForThisProject(string rootDirectoryPath, IContai
//NB: when we move to .net 4, we can remove this and instead use Lazy<Func<PersonBasicEditor.Factory> in the PersonFileType constructor
//builder.Register<Func<PersonBasicEditor.Factory>>(c => () => c.Resolve<PersonBasicEditor.Factory>());
//builder.Register<Func<SessionBasicEditor.Factory>>(c => () => c.Resolve<SessionBasicEditor.Factory>());
builder.RegisterInstance(new MetadataSearch(this)).AsSelf();
});
}

Expand Down
Loading